×

了解内核模块管理 (KMM) 操作符以及如何使用它在 OpenShift Container Platform 集群上部署树外内核模块和设备插件。

关于内核模块管理操作符

内核模块管理 (KMM) 操作符管理、构建、签名和部署 OpenShift Container Platform 集群上的树外内核模块和设备插件。

KMM 添加了一个新的 `Module` CRD,它描述了树外内核模块及其关联的设备插件。您可以使用 `Module` 资源配置如何加载模块,为内核版本定义 `ModuleLoader` 镜像,并包含针对特定内核版本的模块构建和签名说明。

KMM 旨在同时适应多个内核版本,适用于任何内核模块,从而实现无缝节点升级并减少应用程序停机时间。

安装内核模块管理操作符

作为集群管理员,您可以使用 OpenShift CLI 或 Web 控制台安装内核模块管理 (KMM) 操作符。

KMM 操作符在 OpenShift Container Platform 4.12 及更高版本上受支持。在 4.11 版本上安装 KMM 不需要额外的步骤。有关在 4.10 及更早版本上安装 KMM 的详细信息,请参阅“在早期版本的 OpenShift Container Platform 上安装内核模块管理操作符”部分。

使用 Web 控制台安装内核模块管理操作符

作为集群管理员,您可以使用 OpenShift Container Platform Web 控制台安装内核模块管理 (KMM) 操作符。

步骤
  1. 登录到 OpenShift Container Platform Web 控制台。

  2. 安装内核模块管理操作符

    1. 在 OpenShift Container Platform Web 控制台中,单击**操作符** → **OperatorHub**。

    2. 从可用操作符列表中选择**内核模块管理操作符**,然后单击**安装**。

    3. 从**已安装命名空间**列表中,选择 `openshift-kmm` 命名空间。

    4. 单击**安装**。

验证

要验证 KMM 操作符是否已成功安装

  1. 导航到**操作符** → **已安装操作符**页面。

  2. 确保**内核模块管理操作符**列在 `openshift-kmm` 项目中,且**状态**为**InstallSucceeded**。

    在安装过程中,操作符可能会显示**失败**状态。如果安装稍后成功并显示**InstallSucceeded**消息,则可以忽略**失败**消息。

故障排除
  1. 要排除操作符安装问题

    1. 导航到**操作符** → **已安装操作符**页面,并检查**操作符订阅**和**安装计划**选项卡中**状态**下的任何故障或错误。

    2. 导航到**工作负载** → **Pod**页面,并检查 `openshift-kmm` 项目中 Pod 的日志。

使用 CLI 安装内核模块管理操作符

作为集群管理员,您可以使用 OpenShift CLI 安装内核模块管理 (KMM) 操作符。

先决条件
  • 您有一个正在运行的 OpenShift Container Platform 集群。

  • 您已安装 OpenShift CLI ( `oc` )。

  • 您已以具有 `cluster-admin` 权限的用户身份登录到 OpenShift CLI。

步骤
  1. 在 `openshift-kmm` 命名空间中安装 KMM

    1. 创建以下 `Namespace` CR 并保存 YAML 文件,例如 `kmm-namespace.yaml`

      apiVersion: v1
      kind: Namespace
      metadata:
        name: openshift-kmm
    2. 创建以下 `OperatorGroup` CR 并保存 YAML 文件,例如 `kmm-op-group.yaml`

      apiVersion: operators.coreos.com/v1
      kind: OperatorGroup
      metadata:
        name: kernel-module-management
        namespace: openshift-kmm
    3. 创建以下 `Subscription` CR 并保存 YAML 文件,例如 `kmm-sub.yaml`

      apiVersion: operators.coreos.com/v1alpha1
      kind: Subscription
      metadata:
        name: kernel-module-management
        namespace: openshift-kmm
      spec:
        channel: release-1.0
        installPlanApproval: Automatic
        name: kernel-module-management
        source: redhat-operators
        sourceNamespace: openshift-marketplace
        startingCSV: kernel-module-management.v1.0.0
    4. 通过运行以下命令创建订阅对象

      $ oc create -f kmm-sub.yaml
验证
  • 要验证操作符部署是否成功,请运行以下命令

    $ oc get -n openshift-kmm deployments.apps kmm-operator-controller
    示例输出
    NAME                              READY UP-TO-DATE  AVAILABLE AGE
    kmm-operator-controller           1/1   1           1         97s

    操作符可用。

在早期版本的 OpenShift Container Platform 上安装内核模块管理操作符

KMM 运算符支持 OpenShift Container Platform 4.12 及更高版本。对于 4.10 及更低版本,您必须创建一个新的SecurityContextConstraint对象并将其绑定到运算符的ServiceAccount。作为集群管理员,您可以使用 OpenShift CLI 安装内核模块管理 (KMM) 运算符。

先决条件
  • 您有一个正在运行的 OpenShift Container Platform 集群。

  • 您已安装 OpenShift CLI ( `oc` )。

  • 您已以具有 `cluster-admin` 权限的用户身份登录到 OpenShift CLI。

步骤
  1. 在 `openshift-kmm` 命名空间中安装 KMM

    1. 创建以下Namespace CR 并保存 YAML 文件,例如kmm-namespace.yaml文件。

      apiVersion: v1
      kind: Namespace
      metadata:
        name: openshift-kmm
    2. 创建以下SecurityContextConstraint对象并保存 YAML 文件,例如kmm-security-constraint.yaml文件。

      allowHostDirVolumePlugin: false
      allowHostIPC: false
      allowHostNetwork: false
      allowHostPID: false
      allowHostPorts: false
      allowPrivilegeEscalation: false
      allowPrivilegedContainer: false
      allowedCapabilities:
        - NET_BIND_SERVICE
      apiVersion: security.openshift.io/v1
      defaultAddCapabilities: null
      fsGroup:
        type: MustRunAs
      groups: []
      kind: SecurityContextConstraints
      metadata:
        name: restricted-v2
      priority: null
      readOnlyRootFilesystem: false
      requiredDropCapabilities:
        - ALL
      runAsUser:
        type: MustRunAsRange
      seLinuxContext:
        type: MustRunAs
      seccompProfiles:
        - runtime/default
      supplementalGroups:
        type: RunAsAny
      users: []
      volumes:
        - configMap
        - downwardAPI
        - emptyDir
        - persistentVolumeClaim
        - projected
        - secret
    3. 通过运行以下命令将SecurityContextConstraint对象绑定到运算符的ServiceAccount

      $ oc apply -f kmm-security-constraint.yaml
      $ oc adm policy add-scc-to-user kmm-security-constraint -z kmm-operator-controller -n openshift-kmm
    4. 创建以下 `OperatorGroup` CR 并保存 YAML 文件,例如 `kmm-op-group.yaml`

      apiVersion: operators.coreos.com/v1
      kind: OperatorGroup
      metadata:
        name: kernel-module-management
        namespace: openshift-kmm
    5. 创建以下 `Subscription` CR 并保存 YAML 文件,例如 `kmm-sub.yaml`

      apiVersion: operators.coreos.com/v1alpha1
      kind: Subscription
      metadata:
        name: kernel-module-management
        namespace: openshift-kmm
      spec:
        channel: release-1.0
        installPlanApproval: Automatic
        name: kernel-module-management
        source: redhat-operators
        sourceNamespace: openshift-marketplace
        startingCSV: kernel-module-management.v1.0.0
    6. 通过运行以下命令创建订阅对象

      $ oc create -f kmm-sub.yaml
验证
  • 要验证操作符部署是否成功,请运行以下命令

    $ oc get -n openshift-kmm deployments.apps kmm-operator-controller
    示例输出
    NAME                              READY UP-TO-DATE  AVAILABLE AGE
    kmm-operator-controller           1/1   1           1         97s

    操作符可用。

配置内核模块管理运算符

在大多数情况下,无需修改内核模块管理 (KMM) 运算符的默认配置。但是,您可以使用以下步骤修改运算符设置以适应您的环境。

运算符配置设置在运算符命名空间中的kmm-operator-manager-config ConfigMap中。

步骤
  1. 要修改设置,请通过输入以下命令来编辑ConfigMap数据。

    $ oc edit configmap -n "$namespace" kmm-operator-manager-config
    示例输出
    healthProbeBindAddress: :8081
    job:
      gcDelay: 1h
    leaderElection:
      enabled: true
      resourceID: kmm.sigs.x-k8s.io
    webhook:
      disableHTTP2: true  # CVE-2023-44487
      port: 9443
    metrics:
      enableAuthnAuthz: true
      disableHTTP2: true  # CVE-2023-44487
      bindAddress: 0.0.0.0:8443
      secureServing: true
    worker:
      runAsUser: 0
      seLinuxType: spc_t
      setFirmwareClassPath: /var/lib/firmware
    表 1. 运算符配置参数
    参数 描述

    healthProbeBindAddress

    定义运算符监控 kubelet 健康探针的地址。建议值为:8081

    job.gcDelay

    定义成功构建的 Pod 在删除之前应保留的持续时间。此设置没有推荐值。有关此设置的有效值的更多信息,请参阅 ParseDuration

    leaderElection.enabled

    确定是否使用 leader election 来确保任何时候只运行 KMM 运算符的一个副本。更多信息,请参阅 Leases。建议值为true

    leaderElection.resourceID

    确定 leader election 用于持有 leader 锁的资源名称。建议值为kmm.sigs.x-k8s.io

    webhook.disableHTTP2

    如果为true,则禁用 webhook 服务器的 HTTP/2,以此作为对 cve-2023-44487 的缓解措施。建议值为true

    webhook.port

    定义运算符监控 webhook 请求的端口。建议值为9443

    metrics.enableAuthnAuthz

    确定是否使用TokenReviews进行身份验证,并使用SubjectAccessReviews与 kube-apiserver 进行授权。

    对于身份验证和授权,控制器需要具有以下规则的ClusterRole

    • apiGroups: authentication.k8s.io, resources: tokenreviews, verbs: create

    • apiGroups: authorization.k8s.io, resources: subjectaccessreviews, verbs: create

    要抓取指标(例如,使用 Prometheus),客户端需要具有以下规则的ClusterRole

    • nonResourceURLs: "/metrics", verbs: get

    建议值为true

    metrics.disableHTTP2

    如果为true,则禁用指标服务器的 HTTP/2,以此作为对 CVE-2023-44487 的缓解措施。建议值为true

    metrics.bindAddress

    确定指标服务器的绑定地址。如果未指定,则默认为:8080。要禁用指标服务器,请设置为0。建议值为0.0.0.0:8443

    metrics.secureServing

    确定指标是通过 HTTPS 而不是 HTTP 提供服务。建议值为true

    worker.runAsUser

    确定工作容器安全上下文的runAsUser字段的值。更多信息,请参阅 SecurityContext。建议值为9443

    worker.seLinuxType

    确定工作容器安全上下文的seLinuxOptions.type字段的值。更多信息,请参阅 SecurityContext。建议值为spc_t

    worker.setFirmwareClassPath

    将内核的固件搜索路径设置到节点上的/sys/module/firmware_class/parameters/path文件。如果您需要通过 worker 应用设置该值,则建议值为/var/lib/firmware。否则,请取消设置。

  2. 修改设置后,使用以下命令重启控制器。

    $ oc delete pod -n "<namespace>" -l app.kubernetes.io/component=kmm

    的值取决于您最初的安装方法。

附加资源

卸载内核模块

迁移到较新版本或如果它们在节点上引入一些不良副作用时,必须卸载内核模块。

步骤
  • 要卸载使用 KMM 从节点加载的模块,请删除相应的Module资源。然后,KMM 会根据需要创建 worker Pod 来运行modprobe -r并从节点卸载内核模块。

    卸载 worker Pod 时,KMM 需要加载内核模块时使用的所有资源。这包括Module中引用的ServiceAccount以及定义为允许特权 KMM worker Pod 运行的任何 RBAC。它还包括.spec.imageRepoSecret中引用的任何拉取密钥。

    为避免 KMM 无法从节点卸载内核模块的情况,请确保在集群中仍然存在Module资源(包括Terminating状态)时,不会删除这些资源。KMM 包含一个验证性准入 Webhook,该 Webhook 会拒绝删除至少包含一个Module资源的命名空间。

设置内核固件搜索路径

固件搜索路径中所述,Linux 内核接受firmware_class.path参数作为固件的搜索路径。

KMM worker Pod 可以通过在尝试加载 kmod 之前写入 sysfs 来在节点上设置此值。

步骤
  • 要定义固件搜索路径,请在运算符配置中将worker.setFirmwareClassPath设置为/var/lib/firmware

附加资源

卸载内核模块管理运算符

根据 KMM 运算符的安装方式,使用以下步骤之一卸载内核模块管理 (KMM) 运算符。

卸载 Red Hat 目录安装

如果 KMM 是从 Red Hat 目录安装的,请使用此步骤。

步骤

使用以下方法卸载 KMM 运算符

  • 在 OpenShift 控制台的**运算符**→**已安装的运算符**下找到并卸载运算符。

或者,您可以删除 KMM 命名空间中的Subscription资源。

卸载 CLI 安装

如果使用 OpenShift CLI 安装了 KMM 运算符,请使用此命令。

步骤
  • 运行以下命令卸载 KMM 运算符

    $ oc delete -k https://github.com/rh-ecosystem-edge/kernel-module-management/config/default

    使用此命令将删除集群中的Module CRD 和所有Module 实例。

内核模块部署

内核模块管理 (KMM) 监控集群中的NodeModule 资源,以确定是否应在节点上加载或卸载内核模块。

要符合模块的条件,节点必须包含以下内容:

  • 与模块的.spec.selector 字段匹配的标签。

  • 与模块的.spec.moduleLoader.container.kernelMappings 字段中的一个项目匹配的内核版本。

  • 如果在模块中配置了有序升级 (ordered_upgrade.md),则包含与.spec.moduleLoader.container.version 字段匹配的标签。

当 KMM 将节点与Module 资源中配置的所需状态协调时,它会在目标节点上创建工作 Pod 来运行必要的操作。KMM 运算符监控 Pod 的结果并记录信息。运算符使用此信息在成功加载模块时标记Node 对象,并在配置的情况下运行设备插件。

工作 Pod 运行执行以下任务的 KMM worker 二进制文件:

  • 拉取Module 资源中配置的 kmod 镜像。Kmod 镜像是包含.ko 文件的标准 OCI 镜像。

  • 将镜像提取到 Pod 的文件系统中。

  • 使用指定的参数运行modprobe 来执行必要的操作。

Module 自定义资源定义

Module 自定义资源定义 (CRD) 代表一个内核模块,可以通过 kmod 镜像加载到集群中的所有或部分选定节点上。Module 自定义资源 (CR) 指定一个或多个与其兼容的内核版本和节点选择器。

Module 资源的兼容版本列在.spec.moduleLoader.container.kernelMappings 下。内核映射可以匹配literal 版本,也可以使用regexp 同时匹配多个版本。

Module 资源的协调循环运行以下步骤:

  1. 列出所有与.spec.selector 匹配的节点。

  2. 构建在这些节点上运行的所有内核版本的集合。

  3. 对于每个内核版本:

    1. 遍历.spec.moduleLoader.container.kernelMappings 并查找相应的容器镜像名称。如果内核映射已定义buildsign,并且容器镜像尚不存在,则根据需要运行构建、签名 Pod 或两者。

    2. 创建一个工作 Pod 来拉取上一步中确定的容器镜像并运行modprobe

    3. 如果定义了.spec.devicePlugin,则使用.spec.devicePlugin.container 下指定的配置创建一个设备插件 DaemonSet。

  4. 对以下内容运行garbage-collect

    1. 不针对任何节点的已过时的设备插件DaemonSets

    2. 成功的构建 Pod。

    3. 成功的签名 Pod。

设置内核模块之间的软依赖关系

某些配置要求以特定顺序加载多个内核模块才能正常工作,即使这些模块不通过符号直接依赖于彼此。这些称为软依赖关系。depmod 通常不知道这些依赖关系,并且它们不会出现在它生成的文件中。例如,如果mod_amod_b 有软依赖关系,则modprobe mod_a 将不会加载mod_b

您可以通过使用modulesLoadingOrder 字段在 Module 自定义资源定义 (CRD) 中声明软依赖关系来解决这些情况。

# ...
spec:
  moduleLoader:
    container:
      modprobe:
        moduleName: mod_a
        dirName: /opt
        firmwarePath: /firmware
        parameters:
          - param=1
        modulesLoadingOrder:
          - mod_a
          - mod_b

在上面的配置中,工作 Pod 将首先尝试卸载树内mod_b,然后再从 kmod 镜像加载mod_a。当工作 Pod 终止并且mod_a 被卸载时,mod_b 将不会再次加载。

列表中的第一个值(最后加载)必须等效于moduleName

安全性和权限

加载内核模块是一个高度敏感的操作。加载后,内核模块具有所有可能的权限来对节点执行任何类型的操作。

ServiceAccounts 和 SecurityContextConstraints

内核模块管理 (KMM) 创建一个特权工作负载以在节点上加载内核模块。该工作负载需要允许使用privileged SecurityContextConstraint (SCC) 资源的ServiceAccounts

该工作负载的授权模型取决于Module 资源的命名空间及其规范。

  • 如果设置了.spec.moduleLoader.serviceAccountName.spec.devicePlugin.serviceAccountName 字段,则始终使用它们。

  • 如果没有设置这些字段,则:

    • 如果在运算符的命名空间(默认为openshift-kmm)中创建Module 资源,则 KMM 使用其默认的、强大的ServiceAccounts 来运行工作 Pod 和设备插件 Pod。

    • 如果在任何其他命名空间中创建Module 资源,则 KMM 使用命名空间的default ServiceAccount 运行 Pod。除非您手动启用它以使用privileged SCC,否则Module 资源无法运行特权工作负载。

openshift-kmm 是一个受信任的命名空间。

设置 RBAC 权限时,请记住,任何在openshift-kmm 命名空间中创建Module 资源的用户或ServiceAccount 都会导致 KMM 自动在集群中所有潜在节点上运行特权工作负载。

要允许任何ServiceAccount 使用privileged SCC 并运行工作 Pod 或设备插件 Pod,您可以使用oc adm policy 命令,如下例所示:

$ oc adm policy add-scc-to-user privileged -z "${serviceAccountName}" [ -n "${namespace}" ]

Pod 安全标准

OpenShift 运行一个同步机制,该机制会根据使用的安全上下文自动设置命名空间 Pod 安全级别。无需任何操作。

用树外模块替换树内模块

您可以使用内核模块管理 (KMM) 构建可以按需加载或卸载到内核中的内核模块。这些模块扩展了内核的功能,无需重新启动系统。模块可以配置为内置或动态加载。

动态加载的模块包括树内模块和树外 (OOT) 模块。树内模块是 Linux 内核树内部的,也就是说,它们已经是内核的一部分。树外模块位于 Linux 内核树之外。它们通常用于开发和测试目的,例如测试以树内方式交付的内核模块的新版本,或处理不兼容性。

KMM 加载的某些模块可能会替换节点上已加载的树内模块。要在加载您的模块之前卸载树内模块,请将.spec.moduleLoader.container.inTreeModulesToRemove 字段的值设置为要卸载的模块。以下示例演示了所有内核映射的模块替换:

# ...
spec:
  moduleLoader:
    container:
      modprobe:
        moduleName: mod_a

      inTreeModulesToRemove: [mod_a, mod_b]

在这个示例中,moduleLoader pod 使用 inTreeModulesToRemove 在从 moduleLoader 镜像加载 mod_a 之前卸载树内模块 mod_amod_b。当 moduleLoader pod 终止并且 mod_a 被卸载时,mod_b 不会再次加载。

以下是针对特定内核映射的模块替换示例。

# ...
spec:
  moduleLoader:
    container:
      kernelMappings:
        - literal: 6.0.15-300.fc37.x86_64
          containerImage: "some.registry/org/my-kmod:${KERNEL_FULL_VERSION}"
          inTreeModulesToRemove: [<module_name>, <module_name>]

示例模块 CR

以下是带注释的 Module 示例

apiVersion: kmm.sigs.x-k8s.io/v1beta1
kind: Module
metadata:
  name: <my_kmod>
spec:
  moduleLoader:
    container:
      modprobe:
        moduleName: <my_kmod> (1)
        dirName: /opt (2)
        firmwarePath: /firmware (3)
        parameters:  (4)
          - param=1
      kernelMappings:  (5)
        - literal: 6.0.15-300.fc37.x86_64
          containerImage: some.registry/org/my-kmod:6.0.15-300.fc37.x86_64
        - regexp: '^.+\fc37\.x86_64$' (6)
          containerImage: "some.other.registry/org/<my_kmod>:${KERNEL_FULL_VERSION}"
        - regexp: '^.+$' (7)
          containerImage: "some.registry/org/<my_kmod>:${KERNEL_FULL_VERSION}"  (8)
          build:
            buildArgs:  (9)
              - name: ARG_NAME
                value: <some_value>
            secrets:
              - name: <some_kubernetes_secret>  (10)
            baseImageRegistryTLS: (11)
              insecure: false
              insecureSkipTLSVerify: false  (12)
            dockerfileConfigMap:  (13)
              name: <my_kmod_dockerfile>
          sign:
            certSecret:
              name: <cert_secret>  (14)
            keySecret:
              name: <key_secret>  (15)
            filesToSign:
              - /opt/lib/modules/${KERNEL_FULL_VERSION}/<my_kmod>.ko
          registryTLS: (16)
            insecure: false (17)
            insecureSkipTLSVerify: false
    serviceAccountName: <sa_module_loader>  (18)
  devicePlugin:  (19)
    container:
      image: some.registry/org/device-plugin:latest  (20)
      env:
        - name: MY_DEVICE_PLUGIN_ENV_VAR
          value: SOME_VALUE
      volumeMounts:  (21)
        - mountPath: /some/mountPath
          name: <device_plugin_volume>
    volumes:  (22)
      - name: <device_plugin_volume>
        configMap:
          name: <some_configmap>
    serviceAccountName: <sa_device_plugin> (23)
  imageRepoSecret:  (24)
    name: <secret_name>
  selector:
    node-role.kubernetes.io/worker: ""
1 必需。
2 可选。
3 可选:将此路径的内容复制到 kmm-operator-manager-config 配置映射的 worker.setFirmwareClassPath(预设为 /var/lib/firmware)中指定的路径。此操作发生在调用 modprobe 插入内核模块之前。
4 可选。
5 至少需要一个内核项。
6 对于每个运行与正则表达式匹配的内核的节点,KMM 检查您是否包含了标签或摘要。如果您没有在容器镜像中指定标签或摘要,则验证 Webhook 将返回错误,并且不会应用模块。
7 对于任何其他内核,请使用 my-kmod ConfigMap 中的 Dockerfile 构建镜像。
8 包含客户 kmod 的容器镜像。此容器应包含 cp 二进制文件。
9 可选。
10 可选:可以在构建环境的 /run/secrets/some-kubernetes-secret 中获取 some-kubernetes-secret 的值。
11 此字段无效。在构建 kmod 镜像或在 kmod 镜像中签署 kmod 时,有时可能需要从使用不受信任的证书颁发机构 (CA) 签署的证书的服务注册表中提取基础镜像。为了让 KMM 信任该 CA,它还必须通过替换集群的 CA 捆绑包来信任新的 CA。

请参阅“其他资源”以了解如何替换集群的 CA 捆绑包。

12 可选:避免使用此参数。如果设置为 true,则构建将在使用普通 HTTP 拉取 Dockerfile FROM 指令中的镜像时跳过任何 TLS 服务器证书验证。
13 必需。
14 必需:包含具有密钥“cert”的公共安全启动密钥的密钥。
15 必需:包含具有密钥“key”的私有安全启动密钥的密钥。
16 可选:避免使用此参数。如果设置为 true,则允许 KMM 使用普通 HTTP 检查容器镜像是否已存在。
17 可选:避免使用此参数。如果设置为 true,则 KMM 在检查容器镜像是否已存在时将跳过任何 TLS 服务器证书验证。
18 可选。
19 可选。
20 必需:如果存在设备插件部分。
21 可选。
22 可选。
23 可选。
24 可选:用于拉取模块加载器和设备插件镜像。

某些内核模块依赖于随节点操作系统一起提供的其他内核模块。为了避免将这些依赖项复制到 kmod 镜像中,内核模块管理 (KMM) 将 /usr/lib/modules 挂载到构建和工作程序 pod 的文件系统中。

通过从 /opt/usr/lib/modules// 创建到 /usr/lib/modules/ 的符号链接,depmod 可以使用构建节点文件系统上的树内 kmod 来解析依赖项。

在运行时,工作程序 pod 将提取整个镜像,包括 符号链接。该符号链接指向工作程序 pod 中的 /usr/lib/modules/,该 pod 从节点的文件系统挂载。然后,modprobe 可以遵循该链接并根据需要加载树内依赖项。

在以下示例中,host/opt/usr/lib/modules/ 下的符号链接名称。

ARG DTK_AUTO

FROM ${DTK_AUTO} as builder

#
# Build steps
#

FROM ubi9/ubi

ARG KERNEL_FULL_VERSION

RUN dnf update && dnf install -y kmod

COPY --from=builder /usr/src/kernel-module-management/ci/kmm-kmod/kmm_ci_a.ko /opt/lib/modules/${KERNEL_FULL_VERSION}/
COPY --from=builder /usr/src/kernel-module-management/ci/kmm-kmod/kmm_ci_b.ko /opt/lib/modules/${KERNEL_FULL_VERSION}/

# Create the symbolic link
RUN ln -s /lib/modules/${KERNEL_FULL_VERSION} /opt/lib/modules/${KERNEL_FULL_VERSION}/host

RUN depmod -b /opt ${KERNEL_FULL_VERSION}

depmod 根据运行 kmod 镜像构建的节点上存在的内核模块生成依赖文件。

在 KMM 加载内核模块的节点上,modprobe 期望文件位于 /usr/lib/modules/ 下,并且具有相同的文件系统布局。强烈建议构建节点和目标节点共享相同操作系统和发行版。

创建 kmod 镜像

内核模块管理 (KMM) 使用专用构建的 kmod 镜像,这些镜像是包含 .ko 文件的标准 OCI 镜像。.ko 文件的位置必须匹配以下模式:/lib/modules/[kernel-version]/

使用 .ko 文件时,请记住以下几点

  • 在大多数情况下, 应等于 /opt。这是 Module CRD 的默认值。

  • kernel-version 不能为空,并且必须等于为其构建内核模块的内核版本。

运行 depmod

建议在构建过程结束时运行 depmod 以生成 modules.dep.map 文件。如果您的 kmod 镜像包含多个内核模块,并且其中一个模块依赖于另一个模块,则此方法尤其有用。

您必须拥有 Red Hat 订阅才能下载 kernel-devel 包。

步骤
  • 通过运行以下命令为特定内核版本生成 modules.dep.map 文件

    $ depmod -b /opt ${KERNEL_FULL_VERSION}+`.

Dockerfile 示例

如果您在 OpenShift Container Platform 上构建镜像,请考虑使用驱动程序工具包 (DTK)。

有关更多信息,请参阅 使用授权构建

apiVersion: v1
kind: ConfigMap
metadata:
  name: kmm-ci-dockerfile
data:
  dockerfile: |
    ARG DTK_AUTO
    FROM ${DTK_AUTO} as builder
    ARG KERNEL_FULL_VERSION
    WORKDIR /usr/src
    RUN ["git", "clone", "https://github.com/rh-ecosystem-edge/kernel-module-management.git"]
    WORKDIR /usr/src/kernel-module-management/ci/kmm-kmod
    RUN KERNEL_SRC_DIR=/lib/modules/${KERNEL_FULL_VERSION}/build make all
    FROM registry.redhat.io/ubi9/ubi-minimal
    ARG KERNEL_FULL_VERSION
    RUN microdnf install kmod
    COPY --from=builder /usr/src/kernel-module-management/ci/kmm-kmod/kmm_ci_a.ko /opt/lib/modules/${KERNEL_FULL_VERSION}/
    COPY --from=builder /usr/src/kernel-module-management/ci/kmm-kmod/kmm_ci_b.ko /opt/lib/modules/${KERNEL_FULL_VERSION}/
    RUN depmod -b /opt ${KERNEL_FULL_VERSION}
附加资源

在集群中构建

KMM 可以在集群中构建 kmod 镜像。请遵循以下准则

  • 使用内核映射的 build 部分提供构建指令。

  • 将容器镜像的 Dockerfile 复制到 ConfigMap 资源的 dockerfile 密钥下。

  • 确保 ConfigMap 位于与 Module 相同的命名空间中。

KMM 检查 containerImage 字段中指定的镜像名称是否存在。如果存在,则跳过构建。

否则,KMM 将创建一个 Build 资源来构建您的镜像。构建镜像后,KMM 将继续进行 Module 调和。请参见以下示例。

# ...
- regexp: '^.+$'
  containerImage: "some.registry/org/<my_kmod>:${KERNEL_FULL_VERSION}"
  build:
    buildArgs:  (1)
      - name: ARG_NAME
        value: <some_value>
    secrets: (2)
      - name: <some_kubernetes_secret> (3)
    baseImageRegistryTLS:
      insecure: false (4)
      insecureSkipTLSVerify: false (5)
    dockerfileConfigMap:  (6)
      name: <my_kmod_dockerfile>
  registryTLS:
    insecure: false (7)
    insecureSkipTLSVerify: false (8)
1 可选。
2 可选。
3 将在构建 pod 中作为 /run/secrets/some-kubernetes-secret 挂载。
4 可选:避免使用此参数。如果设置为 true,则允许构建使用普通 HTTP 拉取 Dockerfile FROM 指令中的镜像。
5 可选:避免使用此参数。如果设置为 true,则构建将在使用普通 HTTP 拉取 Dockerfile FROM 指令中的镜像时跳过任何 TLS 服务器证书验证。
6 必需。
7 可选:避免使用此参数。如果设置为 true,则允许 KMM 使用普通 HTTP 检查容器镜像是否已存在。
8 可选:避免使用此参数。如果设置为true,KMM 将跳过任何 TLS 服务器证书验证,以检查容器镜像是否存在。

成功的构建 Pod 会立即被垃圾回收,除非在 Operator 配置中设置了job.gcDelay参数。失败的构建 Pod 将始终保留,必须由管理员手动删除才能重新启动构建。

使用驱动程序工具包

驱动程序工具包 (DTK) 是构建 kmod 加载程序镜像的便捷基础镜像。它包含当前在集群中运行的 OpenShift 版本的工具和库。

步骤

将 DTK 用作多阶段Dockerfile 的第一阶段。

  1. 构建内核模块。

  2. .ko文件复制到较小的最终用户镜像中,例如ubi-minimal

  3. 要在集群内构建中利用 DTK,请使用DTK_AUTO构建参数。创建Build资源时,KMM 会自动设置该值。请参见以下示例。

    ARG DTK_AUTO
    FROM ${DTK_AUTO} as builder
    ARG KERNEL_FULL_VERSION
    WORKDIR /usr/src
    RUN ["git", "clone", "https://github.com/rh-ecosystem-edge/kernel-module-management.git"]
    WORKDIR /usr/src/kernel-module-management/ci/kmm-kmod
    RUN KERNEL_SRC_DIR=/lib/modules/${KERNEL_FULL_VERSION}/build make all
    FROM ubi9/ubi-minimal
    ARG KERNEL_FULL_VERSION
    RUN microdnf install kmod
    COPY --from=builder /usr/src/kernel-module-management/ci/kmm-kmod/kmm_ci_a.ko /opt/lib/modules/${KERNEL_FULL_VERSION}/
    COPY --from=builder /usr/src/kernel-module-management/ci/kmm-kmod/kmm_ci_b.ko /opt/lib/modules/${KERNEL_FULL_VERSION}/
    RUN depmod -b /opt ${KERNEL_FULL_VERSION}
附加资源

使用内核模块管理 (KMM) 进行签名

在启用安全启动的系统上,所有内核模块 (kmod) 都必须使用注册到机器所有者密钥 (MOK) 数据库中的公钥/私钥对进行签名。作为发行版一部分分发的驱动程序应已由发行版的私钥签名,但对于树外构建的内核模块,KMM 支持使用内核映射的sign部分对内核模块进行签名。

有关使用安全启动的更多详细信息,请参阅生成公钥和私钥对

先决条件
  • 正确 (DER) 格式的公钥私钥对。

  • 至少一个启用安全启动的节点,其 MOK 数据库中注册了公钥。

  • 预构建的驱动程序容器镜像,或在集群内构建一个所需的源代码和Dockerfile

添加安全启动密钥

要使用 KMM 内核模块管理 (KMM) 对内核模块进行签名,需要证书和私钥。有关如何创建这些密钥的详细信息,请参阅生成公钥和私钥对

有关如何提取公钥和私钥对的详细信息,请参阅使用私钥签名内核模块。使用步骤 1 到 4 将密钥提取到文件中。

步骤
  1. 创建包含证书的sb_cert.cer文件和包含私钥的sb_cert.priv文件。

    $ openssl req -x509 -new -nodes -utf8 -sha256 -days 36500 -batch -config configuration_file.config -outform DER -out my_signing_key_pub.der -keyout my_signing_key.priv
  2. 使用以下方法之一添加文件:

    • 直接添加文件作为密钥

      $ oc create secret generic my-signing-key --from-file=key=<my_signing_key.priv>
      $ oc create secret generic my-signing-key-pub --from-file=cert=<my_signing_key_pub.der>
    • 通过 base64 编码添加文件

      $ cat sb_cert.priv | base64 -w 0 > my_signing_key2.base64
      $ cat sb_cert.cer | base64 -w 0 > my_signing_key_pub.base64
  3. 将编码文本添加到 YAML 文件

    apiVersion: v1
    kind: Secret
    metadata:
      name: my-signing-key-pub
      namespace: default (1)
    type: Opaque
    data:
      cert: <base64_encoded_secureboot_public_key>
    
    ---
    apiVersion: v1
    kind: Secret
    metadata:
      name: my-signing-key
      namespace: default (1)
    type: Opaque
    data:
      key: <base64_encoded_secureboot_private_key>
    1 namespace - 将default替换为有效的命名空间。
  4. 应用 YAML 文件

    $ oc apply -f <yaml_filename>

检查密钥

添加密钥后,必须检查密钥以确保它们已正确设置。

步骤
  1. 检查以确保公钥密钥已正确设置。

    $ oc get secret -o yaml <certificate secret name> | awk '/cert/{print $2; exit}' | base64 -d  | openssl x509 -inform der -text

    这应该显示一个具有序列号、颁发者、主题等的证书。

  2. 检查以确保私钥密钥已正确设置。

    $ oc get secret -o yaml <private key secret name> | awk '/key/{print $2; exit}' | base64 -d

    这应该显示包含在-----BEGIN PRIVATE KEY----------END PRIVATE KEY-----行中的密钥。

在预构建镜像中签名 kmod

如果您有预构建的镜像(例如,硬件供应商分发的镜像或在其他地方构建的镜像),请使用此过程。

以下 YAML 文件将公钥/私钥对添加为具有所需密钥名称的密钥 - 私钥为key,公钥为cert 的密钥。然后,集群下载unsignedImage镜像,打开它,签名filesToSign中列出的内核模块,将它们添加回来,并将生成的镜像作为containerImage推送。

然后,KMM 将签名的 kmod 加载到与选择器匹配的所有节点上。kmod 将成功加载到 MOK 数据库中具有公钥的任何节点上,以及任何未启用安全启动的节点上,这些节点将忽略签名。

先决条件
  • keySecretcertSecret密钥已在与其余资源相同的命名空间中创建。

步骤
  • 应用 YAML 文件

    ---
    apiVersion: kmm.sigs.x-k8s.io/v1beta1
    kind: Module
    metadata:
      name: example-module
    spec:
      moduleLoader:
        serviceAccountName: default
        container:
          modprobe: (1)
            moduleName: '<module_name>'
          kernelMappings:
            # the kmods will be deployed on all nodes in the cluster with a kernel that matches the regexp
            - regexp: '^.*\.x86_64$'
              # the container to produce containing the signed kmods
              containerImage: <image_name> (2)
              sign:
                # the image containing the unsigned kmods (we need this because we are not building the kmods within the cluster)
                unsignedImage: <image_name> (3)
                keySecret: # a secret holding the private secureboot key with the key 'key'
                  name: <private_key_secret_name>
                certSecret: # a secret holding the public secureboot key with the key 'cert'
                  name: <certificate_secret_name>
                filesToSign: # full path within the unsignedImage container to the kmod(s) to sign
                  - /opt/lib/modules/4.18.0-348.2.1.el8_5.x86_64/kmm_ci_a.ko
      imageRepoSecret:
        # the name of a secret containing credentials to pull unsignedImage and push containerImage to the registry
        name: repo-pull-secret
      selector:
        kubernetes.io/arch: amd64
1 要加载的 kmod 的名称。
2 容器镜像的名称。例如,quay.io/myuser/my-driver:<kernelversion
3 未签名镜像的名称。例如,quay.io/myuser/my-driver:<kernelversion

构建和签名 kmod 镜像

如果您有源代码并且必须先构建镜像,请使用此过程。

以下 YAML 文件使用存储库中的源代码构建新的容器镜像。生成的镜像将使用临时名称保存回注册表中,然后使用sign部分中的参数对该临时镜像进行签名。

临时镜像名称基于最终镜像名称,并设置为<containerImage>:<tag>-<namespace>_<module name>_kmm_unsigned

例如,使用以下 YAML 文件,内核模块管理 (KMM) 将构建一个名为example.org/repository/minimal-driver:final-default_example-module_kmm_unsigned的镜像,其中包含未签名的 kmod 并将其推送到注册表。然后,它创建一个名为example.org/repository/minimal-driver:final的第二个镜像,其中包含签名的 kmod。正是这个第二个镜像被工作 Pod 拉取,并包含要在集群节点上加载的 kmod。

签名后,您可以安全地从注册表中删除临时镜像。如有需要,它将被重新构建。

先决条件
  • keySecretcertSecret密钥已在与其余资源相同的命名空间中创建。

步骤
  • 应用 YAML 文件

    ---
    apiVersion: v1
    kind: ConfigMap
    metadata:
      name: example-module-dockerfile
      namespace: <namespace> (1)
    data:
      Dockerfile: |
        ARG DTK_AUTO
        ARG KERNEL_VERSION
        FROM ${DTK_AUTO} as builder
        WORKDIR /build/
        RUN git clone -b main --single-branch https://github.com/rh-ecosystem-edge/kernel-module-management.git
        WORKDIR kernel-module-management/ci/kmm-kmod/
        RUN make
        FROM registry.access.redhat.com/ubi9/ubi:latest
        ARG KERNEL_VERSION
        RUN yum -y install kmod && yum clean all
        RUN mkdir -p /opt/lib/modules/${KERNEL_VERSION}
        COPY --from=builder /build/kernel-module-management/ci/kmm-kmod/*.ko /opt/lib/modules/${KERNEL_VERSION}/
        RUN /usr/sbin/depmod -b /opt
    ---
    apiVersion: kmm.sigs.x-k8s.io/v1beta1
    kind: Module
    metadata:
      name: example-module
      namespace: <namespace> (1)
    spec:
      moduleLoader:
        serviceAccountName: default (2)
        container:
          modprobe:
            moduleName: simple_kmod
          kernelMappings:
            - regexp: '^.*\.x86_64$'
              containerImage: <final_driver_container_name>
              build:
                dockerfileConfigMap:
                  name: example-module-dockerfile
              sign:
                keySecret:
                  name: <private_key_secret_name>
                certSecret:
                  name: <certificate_secret_name>
                filesToSign:
                  - /opt/lib/modules/4.18.0-348.2.1.el8_5.x86_64/kmm_ci_a.ko
      imageRepoSecret: (3)
        name: repo-pull-secret
      selector: # top-level selector
        kubernetes.io/arch: amd64
1 default替换为有效的命名空间。
2 默认的serviceAccountName没有运行特权模块所需的权限。有关创建服务帐户的信息,请参阅本节“附加资源”中的“创建服务帐户”。
3 用作DaemonSet对象中的imagePullSecrets,以及构建和签名功能的拉取和推送。
附加资源

KMM 集线器和辐条

在集线器和辐条场景中,许多辐条集群连接到中央强大的集线器集群。内核模块管理 (KMM) 依赖于 Red Hat Advanced Cluster Management (RHACM) 才能在集线器和辐条环境中运行。

KMM 通过解耦 KMM 功能与集线器和辐条环境兼容。提供了一个ManagedClusterModule自定义资源定义 (CRD) 来包装现有的Module CRD 并将其扩展到选择辐条集群。还提供了 KMM-Hub,这是一个新的独立控制器,可在集线器集群上构建镜像和签名模块。

在集线器和辐条设置中,辐条是专注的、资源受限的集群,由集线器集群集中管理。辐条运行 KMM 的单集群版本,其中资源密集型功能已禁用。为了使 KMM 适应此环境,应将辐条上运行的工作负载减少到最低限度,而集线器负责处理费时的任务。

构建内核模块镜像和签名.ko文件应在集线器上运行。模块加载器和设备插件DaemonSets的调度只能在辐条上进行。

KMM-Hub

KMM 项目提供 KMM-Hub,这是一个专用于 Hub 集群的 KMM 版本。KMM-Hub 监控运行在 Spoke 上的所有内核版本,并确定集群中应该接收内核模块的节点。

KMM-Hub 运行所有计算密集型任务,例如镜像构建和 kmod 签名,并准备精简的 Module,通过 RHACM 传输到 Spoke。

KMM-Hub 不能用于在 Hub 集群上加载内核模块。要加载内核模块,请安装 KMM 的常规版本。

附加资源

安装 KMM-Hub

您可以使用以下方法之一安装 KMM-Hub

  • 使用 Operator Lifecycle Manager (OLM)

  • 创建 KMM 资源

附加资源

使用 Operator Lifecycle Manager 安装 KMM-Hub

使用 OpenShift 控制台的 **Operators** 部分安装 KMM-Hub。

通过创建 KMM 资源安装 KMM-Hub

步骤
  • 如果您想以编程方式安装 KMM-Hub,可以使用以下资源创建 NamespaceOperatorGroupSubscription 资源

---
apiVersion: v1
kind: Namespace
metadata:
  name: openshift-kmm-hub
---
apiVersion: operators.coreos.com/v1
kind: OperatorGroup
metadata:
  name: kernel-module-management-hub
  namespace: openshift-kmm-hub
---
apiVersion: operators.coreos.com/v1alpha1
kind: Subscription
metadata:
  name: kernel-module-management-hub
  namespace: openshift-kmm-hub
spec:
  channel: stable
  installPlanApproval: Automatic
  name: kernel-module-management-hub
  source: redhat-operators
  sourceNamespace: openshift-marketplace

使用 ManagedClusterModule CRD

使用 ManagedClusterModule 自定义资源定义 (CRD) 来配置在 Spoke 集群上部署内核模块。此 CRD 是集群范围的,它包含一个 Module 规范,并添加了以下附加字段

apiVersion: hub.kmm.sigs.x-k8s.io/v1beta1
kind: ManagedClusterModule
metadata:
  name: <my-mcm>
  # No namespace, because this resource is cluster-scoped.
spec:
  moduleSpec: (1)
    selector: (2)
      node-wants-my-mcm: 'true'

  spokeNamespace: <some-namespace> (3)

  selector: (4)
    wants-my-mcm: 'true'
1 moduleSpec:包含 moduleLoaderdevicePlugin 部分,类似于 Module 资源。
2 选择 ManagedCluster 中的节点。
3 指定应在哪个命名空间中创建 Module
4 选择 ManagedCluster 对象。

如果 .spec.moduleSpec 中存在构建或签名指令,则这些 Pod 将在操作符命名空间中的 Hub 集群上运行。

.spec.selector 匹配一个或多个 ManagedCluster 资源时,KMM-Hub 将在相应的命名空间中创建一个 ManifestWork 资源。ManifestWork 包含一个精简的 Module 资源,保留了内核映射,但删除了所有 buildsign 子部分。包含以标签结尾的镜像名称的 containerImage 字段将替换为其摘要等效项。

在 Spoke 上运行 KMM

在 Spoke 上安装内核模块管理 (KMM) 后,无需进一步操作。从 Hub 创建 ManagedClusterModule 对象可在 Spoke 集群上部署内核模块。

步骤

您可以通过 RHACM Policy 对象在 Spoke 集群上安装 KMM。除了从 OperatorHub 安装 KMM 并以轻量级 Spoke 模式运行它之外,Policy 还配置了 RHACM 代理管理 Module 资源所需的额外 RBAC。

  • 使用以下 RHACM策略在 Spoke 集群上安装 KMM

    ---
    apiVersion: policy.open-cluster-management.io/v1
    kind: Policy
    metadata:
      name: install-kmm
    spec:
      remediationAction: enforce
      disabled: false
      policy-templates:
        - objectDefinition:
            apiVersion: policy.open-cluster-management.io/v1
            kind: ConfigurationPolicy
            metadata:
              name: install-kmm
            spec:
              severity: high
              object-templates:
              - complianceType: mustonlyhave
                objectDefinition:
                  apiVersion: v1
                  kind: Namespace
                  metadata:
                    name: openshift-kmm
              - complianceType: mustonlyhave
                objectDefinition:
                  apiVersion: operators.coreos.com/v1
                  kind: OperatorGroup
                  metadata:
                    name: kmm
                    namespace: openshift-kmm
                  spec:
                    upgradeStrategy: Default
              - complianceType: mustonlyhave
                objectDefinition:
                  apiVersion: operators.coreos.com/v1alpha1
                  kind: Subscription
                  metadata:
                    name: kernel-module-management
                    namespace: openshift-kmm
                  spec:
                    channel: stable
                    config:
                      env:
                        - name: KMM_MANAGED (1)
                          value: "1"
                    installPlanApproval: Automatic
                    name: kernel-module-management
                    source: redhat-operators
                    sourceNamespace: openshift-marketplace
              - complianceType: mustonlyhave
                objectDefinition:
                  apiVersion: rbac.authorization.k8s.io/v1
                  kind: ClusterRole
                  metadata:
                    name: kmm-module-manager
                  rules:
                    - apiGroups: [kmm.sigs.x-k8s.io]
                      resources: [modules]
                      verbs: [create, delete, get, list, patch, update, watch]
              - complianceType: mustonlyhave
                objectDefinition:
                  apiVersion: rbac.authorization.k8s.io/v1
                  kind: ClusterRoleBinding
                  metadata:
                    name: klusterlet-kmm
                  subjects:
                  - kind: ServiceAccount
                    name: klusterlet-work-sa
                    namespace: open-cluster-management-agent
                  roleRef:
                    kind: ClusterRole
                    name: kmm-module-manager
                    apiGroup: rbac.authorization.k8s.io
    ---
    apiVersion: apps.open-cluster-management.io/v1
    kind: PlacementRule
    metadata:
      name: all-managed-clusters
    spec:
      clusterSelector: (2)
        matchExpressions: []
    ---
    apiVersion: policy.open-cluster-management.io/v1
    kind: PlacementBinding
    metadata:
      name: install-kmm
    placementRef:
      apiGroup: apps.open-cluster-management.io
      kind: PlacementRule
      name: all-managed-clusters
    subjects:
      - apiGroup: policy.open-cluster-management.io
        kind: Policy
        name: install-kmm
    1 在 Spoke 集群上运行 KMM 时,需要此环境变量。
    2 可以自定义 spec.clusterSelector 字段以仅定位选定的集群。

自定义内核模块的升级

在对节点进行维护操作(包括根据需要重新引导节点)时,使用此过程升级内核模块。为了最大程度地减少对集群中运行的工作负载的影响,请一次一个节点地顺序运行内核升级过程。

此过程需要了解使用内核模块的工作负载,并且必须由集群管理员管理。

先决条件
  • 升级前,在内核模块使用的所有节点上设置 kmm.node.kubernetes.io/version-module.<module_namespace>.<module_name>=$moduleVersion 标签。

  • 终止节点上所有用户应用程序工作负载,或将其移动到另一个节点。

  • 卸载当前加载的内核模块。

  • 确保在卸载内核模块之前,用户工作负载(在集群中访问内核模块的应用程序)未在节点上运行,并且在新内核模块版本加载后,工作负载已恢复在节点上运行。

步骤
  1. 确保卸载节点上 KMM 管理的设备插件。

  2. 更新 Module 自定义资源 (CR) 中的以下字段

    • containerImage(到相应的内核版本)

    • version

      更新必须是原子的;也就是说,必须同时更新 containerImageversion 字段。

  3. 终止正在升级的节点上使用内核模块的任何工作负载。

  4. 删除节点上的 kmm.node.kubernetes.io/version-module.<module_namespace>.<module_name> 标签。运行以下命令以从节点卸载内核模块

    $ oc label node/<node_name> kmm.node.kubernetes.io/version-module.<module_namespace>.<module_name>-
  5. 如果需要,作为集群管理员,执行内核模块升级所需的任何其他维护。

    如果不需要其他升级,您可以通过将 kmm.node.kubernetes.io/version-module.<module_namespace>.<module_name> 标签值更新为在 Module 中设置的新 $moduleVersion 来跳过步骤 3 到 6。

  6. 运行以下命令以向节点添加 kmm.node.kubernetes.io/version-module.<module_namespace>.<module_name>=$moduleVersion 标签。$moduleVersion 必须等于 Module CR 中 version 字段的新值。

    $ oc label node/<node_name> kmm.node.kubernetes.io/version-module.<module_namespace>.<module_name>=<desired_version>

    由于 Kubernetes 在标签名称方面的限制,Module 名称和命名空间的组合长度不能超过 39 个字符。

  7. 恢复节点上利用内核模块的任何工作负载。

  8. 重新加载节点上 KMM 管理的设备插件。

Day 1 内核模块加载

内核模块管理 (KMM) 通常是 Day 2 运算符。内核模块仅在 Linux (RHCOS) 服务器完全初始化后才加载。但是,在某些情况下,必须在早期阶段加载内核模块。Day 1 功能允许您使用机器配置运算符 (MCO) 在 Linux systemd 初始化阶段加载内核模块。

附加资源

Day 1 支持的用例

Day 1 功能支持有限数量的用例。主要用例是在 NetworkManager 服务初始化之前允许加载树外 (OOT) 内核模块。它不支持在 initramfs 阶段加载内核模块。

以下是 Day 1 功能所需的条件

  • 内核中未加载内核模块。

  • 树内内核模块已加载到内核中,但可以卸载并替换为 OOT 内核模块。这意味着树内模块未被任何其他内核模块引用。

  • 为了使 Day 1 功能正常工作,节点必须具有功能正常的网络接口,即该接口的树内内核驱动程序。OOT 内核模块可以是将替换功能网络驱动程序的网络驱动程序。

OOT 内核模块加载流程

树外 (OOT) 内核模块的加载利用了机器配置运算符 (MCO)。流程顺序如下

步骤
  1. MachineConfig 资源应用于现有的运行集群。为了识别需要更新的必要节点,您必须创建一个合适的 MachineConfigPool 资源。

  2. MCO逐个节点地应用重新引导。在任何重新引导的节点上,都会部署两个新的 systemd 服务:pull 服务和 load 服务。

  3. load 服务配置为在 NetworkConfiguration 服务之前运行。该服务尝试拉取预定义的内核模块镜像,然后使用该镜像卸载树内模块并加载树外内核模块。

  4. pull 服务配置为在 NetworkManager 服务之后运行。该服务检查预配置的内核模块镜像是否位于节点的文件系统上。如果存在,则服务正常退出,服务器继续引导过程。如果不存在,则将其拉取到节点上,然后重新引导节点。

内核模块镜像

Day 1 功能使用与 Day 2 KMM 版本利用的相同基于 DTK 的镜像。树外内核模块应位于 /opt/lib/modules/${kernelVersion} 下。

附加资源

树内模块替换

Day 1 功能始终尝试用树外版本替换树内内核模块。如果树内内核模块未加载,则流程不受影响;服务继续并加载树外内核模块。

MCO YAML 创建

KMM 提供了一个 API 来为 Day 1 功能创建 MCO YAML 清单。

ProduceMachineConfig(machineConfigName, machineConfigPoolRef, kernelModuleImage, kernelModuleName string) (string, error)

返回的输出是待应用的 MCO YAML 清单的字符串表示。应用此 YAML 的责任在于客户。

参数如下:

machineConfigName

MCO YAML 清单的名称。此参数设置为 MCO YAML 清单元数据的 name 参数。

machineConfigPoolRef

用于标识目标节点的 MachineConfigPool 名称。

kernelModuleImage

包含树外内核模块的容器镜像的名称。

kernelModuleName

树外内核模块的名称。此参数用于卸载树内内核模块(如果已加载到内核中)和加载树外内核模块。

API 位于 KMM 源代码的 pkg/mcproducer 包下。无需运行 KMM 运算符即可使用 Day 1 功能。您只需将 pkg/mcproducer 包导入到他们的运算符/实用程序代码中,调用 API 并将生成的 MCO YAML 应用到集群即可。

MachineConfigPool

MachineConfigPool 标识受应用的 MCO 影响的节点集合。

kind: MachineConfigPool
metadata:
  name: sfc
spec:
  machineConfigSelector: (1)
    matchExpressions:
      - {key: machineconfiguration.openshift.io/role, operator: In, values: [worker, sfc]}
  nodeSelector: (2)
    matchLabels:
      node-role.kubernetes.io/sfc: ""
  paused: false
  maxUnavailable: 1
1 匹配 MachineConfig 中的标签。
2 匹配节点上的标签。

OCP 集群中存在预定义的 MachineConfigPools

  • worker:针对集群中的所有工作节点

  • master:针对集群中的所有主节点

定义以下 MachineConfig 来定位主 MachineConfigPool

metadata:
  labels:
    machineconfiguration.opensfhit.io/role: master

定义以下 MachineConfig 来定位工作 MachineConfigPool

metadata:
  labels:
    machineconfiguration.opensfhit.io/role: worker

调试和故障排除

如果驱动程序容器中的 kmod 未签名或使用错误的密钥签名,则容器可能进入 PostStartHookErrorCrashLoopBackOff 状态。您可以通过在容器上运行 oc describe 命令来验证,在这种情况下,它会显示以下消息

modprobe: ERROR: could not insert '<your_kmod_name>': Required key not available

KMM 固件支持

内核模块有时需要从文件系统加载固件文件。KMM 支持将固件文件从 kmod 镜像复制到节点的文件系统。

在运行 modprobe 命令插入内核模块之前,.spec.moduleLoader.container.modprobe.firmwarePath 的内容将复制到节点上的 /var/lib/firmware 路径。

在 pod 终止时,运行 modprobe -r 命令卸载内核模块之前,将删除该位置的所有文件和空目录。

配置节点上的查找路径

在 OpenShift Container Platform 节点上,固件的默认查找路径集不包括 /var/lib/firmware 路径。

步骤
  1. 使用 Machine Config Operator 创建包含 /var/lib/firmware 路径的 MachineConfig 自定义资源 (CR)。

    apiVersion: machineconfiguration.openshift.io/v1
    kind: MachineConfig
    metadata:
      labels:
        machineconfiguration.openshift.io/role: worker (1)
      name: 99-worker-kernel-args-firmware-path
    spec:
      kernelArguments:
        - 'firmware_class.path=/var/lib/firmware'
    1 您可以根据需要配置标签。对于单节点 OpenShift,请使用 control-panemaster 对象。
  2. 应用 MachineConfig CR 后,节点将自动重新引导。

附加资源

构建 kmod 镜像

步骤
  • 除了构建内核模块本身之外,还要在构建器镜像中包含二进制固件。

    FROM registry.redhat.io/ubi9/ubi-minimal as builder
    
    # Build the kmod
    
    RUN ["mkdir", "/firmware"]
    RUN ["curl", "-o", "/firmware/firmware.bin", "https://artifacts.example.com/firmware.bin"]
    
    FROM registry.redhat.io/ubi9/ubi-minimal
    
    # Copy the kmod, install modprobe, run depmod
    
    COPY --from=builder /firmware /firmware

调整模块资源

步骤
  • Module 自定义资源 (CR) 中设置 .spec.moduleLoader.container.modprobe.firmwarePath

    apiVersion: kmm.sigs.x-k8s.io/v1beta1
    kind: Module
    metadata:
      name: my-kmod
    spec:
      moduleLoader:
        container:
          modprobe:
            moduleName: my-kmod  # Required
    
            firmwarePath: /firmware (1)
    1 可选:将 /firmware/* 复制到节点上的 /var/lib/firmware/

Day 0 到 Day 2 kmod 安装

您可以在 Day 0 到 Day 2 操作期间安装一些内核模块 (kmod),而无需使用内核模块管理 (KMM)。这可以帮助将 kmod 过渡到 KMM。

使用以下标准来确定合适的 kmod 安装。

Day 0

节点在集群中变为 Ready 所需的最基本 kmod。这些类型的 kmod 示例包括:

  • 在引导过程中挂载 rootFS 所需的存储驱动程序

  • 机器访问引导节点上的 machine-config-server 以拉取点火并加入集群所需的网络驱动程序

Day 1

节点在集群中变为 Ready 不需要但节点处于 Ready 状态时无法卸载的 kmod。

此类 kmod 的一个示例是替换旧的树内驱动程序的树外 (OOT) 网络驱动程序,以便在 NetworkManager 依赖它的同时充分利用 NIC 的潜力。当节点处于 Ready 状态时,由于 NetworkManager 的依赖关系,您无法卸载驱动程序。

Day 2

可以动态加载到内核或从中移除的 kmod,而不会干扰集群基础设施(例如连接性)。

这些类型的 kmod 的示例包括:

  • GPU 运算符

  • 辅助网络适配器

  • 现场可编程门阵列 (FPGA)

分层背景

当在集群中安装 Day 0 kmod 时,分层将通过 Machine Config Operator (MCO) 应用,OpenShift Container Platform 升级不会触发节点升级。

只有在向驱动程序添加新功能时才需要重新编译驱动程序,因为节点的操作系统将保持不变。

生命周期管理

当驱动程序允许时,您可以利用 KMM 来管理 kmod 的 Day 0 到 Day 2 生命周期,而无需重新引导。

如果升级需要重新引导节点,例如,当需要重建 initramfs 文件时,此方法将不起作用。

使用以下任一选项进行生命周期管理。

将kmod视为树内驱动程序

当您需要升级kmod时,可以使用此方法。在这种情况下,将kmod视为树内驱动程序,并在集群中创建一个包含inTreeRemoval字段的Module,以卸载旧版本的驱动程序。

请注意将kmod视为树内驱动程序的以下特性:

  • 由于KMM尝试同时卸载和加载所有选定节点上的kmod,因此可能会出现停机时间。

  • 如果卸载驱动程序导致节点失去连接,则此方法有效,因为KMM使用单个Pod来卸载和加载驱动程序。

使用有序升级

您可以使用有序升级 (ordered_upgrade.md) 在集群中创建一个表示kmod的版本化Module,这不会产生任何影响,因为kmod已加载。

请注意使用有序升级的以下特性:

  • 由于您可以控制升级的速度以及同时升级的节点数量,因此不会出现集群停机时间;因此,可以实现无停机升级。

  • 如果卸载驱动程序导致失去与节点的连接,则此方法无效,因为KMM创建两个不同的工作Pod用于卸载,另一个用于加载。这些Pod将无法调度。

KMM故障排除

在对KMM安装问题进行故障排除时,您可以监控日志以确定问题发生在哪个阶段。然后,检索与该阶段相关的诊断数据。

读取Operator日志

您可以使用oc logs命令读取Operator日志,如下例所示。

KMM控制器示例命令
$ oc logs -fn openshift-kmm deployments/kmm-operator-controller
KMM Webhook服务器示例命令
$ oc logs -fn openshift-kmm deployments/kmm-operator-webhook-server
KMM-Hub控制器示例命令
$ oc logs -fn openshift-kmm-hub deployments/kmm-operator-hub-controller
KMM-Hub Webhook服务器示例命令
$ oc logs -fn openshift-kmm deployments/kmm-operator-hub-webhook-server

观察事件

使用以下方法查看KMM事件。

构建和签名

每当KMM启动kmod镜像构建或观察其结果时,它都会发布事件。这些事件附加到Module对象,并且可在oc describe module命令输出的末尾找到,如下例所示。

$ oc describe modules.kmm.sigs.x-k8s.io kmm-ci-a
[...]
Events:
  Type    Reason          Age                From  Message
  ----    ------          ----               ----  -------
  Normal  BuildCreated    2m29s              kmm   Build created for kernel 6.6.2-201.fc39.x86_64
  Normal  BuildSucceeded  63s                kmm   Build job succeeded for kernel 6.6.2-201.fc39.x86_64
  Normal  SignCreated     64s (x2 over 64s)  kmm   Sign created for kernel 6.6.2-201.fc39.x86_64
  Normal  SignSucceeded   57s                kmm   Sign job succeeded for kernel 6.6.2-201.fc39.x86_64

模块加载或卸载

每当KMM成功加载或卸载节点上的内核模块时,它都会发布事件。这些事件附加到Node对象,并且可在oc describe node命令输出的末尾找到,如下例所示。

$ oc describe node my-node
[...]
Events:
  Type    Reason          Age    From  Message
  ----    ------          ----   ----  -------
[...]
  Normal  ModuleLoaded    4m17s  kmm   Module default/kmm-ci-a loaded into the kernel
  Normal  ModuleUnloaded  2s     kmm   Module default/kmm-ci-a unloaded from the kernel

使用must-gather工具

oc adm must-gather命令是收集支持包并向Red Hat支持提供调试信息的推荐方法。通过使用以下部分中所述的适当参数运行命令来收集特定信息。

收集KMM数据

步骤
  1. 收集KMM Operator控制器管理器的数据

    1. 设置MUST_GATHER_IMAGE变量

      $ export MUST_GATHER_IMAGE=$(oc get deployment -n openshift-kmm kmm-operator-controller -ojsonpath='{.spec.template.spec.containers[?(@.name=="manager")].env[?(@.name=="RELATED_IMAGE_MUST_GATHER")].value}')
      $ oc adm must-gather --image="${MUST_GATHER_IMAGE}" -- /usr/bin/gather

      如果您在自定义命名空间中安装了KMM,请使用-n <namespace>开关指定命名空间。

    2. 运行must-gather工具

      $ oc adm must-gather --image="${MUST_GATHER_IMAGE}" -- /usr/bin/gather
  2. 查看Operator日志

    $ oc logs -fn openshift-kmm deployments/kmm-operator-controller
    示例输出
    I0228 09:36:37.352405       1 request.go:682] Waited for 1.001998746s due to client-side throttling, not priority and fairness, request: GET:https://172.30.0.1:443/apis/machine.openshift.io/v1beta1?timeout=32s
    I0228 09:36:40.767060       1 listener.go:44] kmm/controller-runtime/metrics "msg"="Metrics server is starting to listen" "addr"="127.0.0.1:8080"
    I0228 09:36:40.769483       1 main.go:234] kmm/setup "msg"="starting manager"
    I0228 09:36:40.769907       1 internal.go:366] kmm "msg"="Starting server" "addr"={"IP":"127.0.0.1","Port":8080,"Zone":""} "kind"="metrics" "path"="/metrics"
    I0228 09:36:40.770025       1 internal.go:366] kmm "msg"="Starting server" "addr"={"IP":"::","Port":8081,"Zone":""} "kind"="health probe"
    I0228 09:36:40.770128       1 leaderelection.go:248] attempting to acquire leader lease openshift-kmm/kmm.sigs.x-k8s.io...
    I0228 09:36:40.784396       1 leaderelection.go:258] successfully acquired lease openshift-kmm/kmm.sigs.x-k8s.io
    I0228 09:36:40.784876       1 controller.go:185] kmm "msg"="Starting EventSource" "controller"="Module" "controllerGroup"="kmm.sigs.x-k8s.io" "controllerKind"="Module" "source"="kind source: *v1beta1.Module"
    I0228 09:36:40.784925       1 controller.go:185] kmm "msg"="Starting EventSource" "controller"="Module" "controllerGroup"="kmm.sigs.x-k8s.io" "controllerKind"="Module" "source"="kind source: *v1.DaemonSet"
    I0228 09:36:40.784968       1 controller.go:185] kmm "msg"="Starting EventSource" "controller"="Module" "controllerGroup"="kmm.sigs.x-k8s.io" "controllerKind"="Module" "source"="kind source: *v1.Build"
    I0228 09:36:40.785001       1 controller.go:185] kmm "msg"="Starting EventSource" "controller"="Module" "controllerGroup"="kmm.sigs.x-k8s.io" "controllerKind"="Module" "source"="kind source: *v1.Job"
    I0228 09:36:40.785025       1 controller.go:185] kmm "msg"="Starting EventSource" "controller"="Module" "controllerGroup"="kmm.sigs.x-k8s.io" "controllerKind"="Module" "source"="kind source: *v1.Node"
    I0228 09:36:40.785039       1 controller.go:193] kmm "msg"="Starting Controller" "controller"="Module" "controllerGroup"="kmm.sigs.x-k8s.io" "controllerKind"="Module"
    I0228 09:36:40.785458       1 controller.go:185] kmm "msg"="Starting EventSource" "controller"="PodNodeModule" "controllerGroup"="" "controllerKind"="Pod" "source"="kind source: *v1.Pod"
    I0228 09:36:40.786947       1 controller.go:185] kmm "msg"="Starting EventSource" "controller"="PreflightValidation" "controllerGroup"="kmm.sigs.x-k8s.io" "controllerKind"="PreflightValidation" "source"="kind source: *v1beta1.PreflightValidation"
    I0228 09:36:40.787406       1 controller.go:185] kmm "msg"="Starting EventSource" "controller"="PreflightValidation" "controllerGroup"="kmm.sigs.x-k8s.io" "controllerKind"="PreflightValidation" "source"="kind source: *v1.Build"
    I0228 09:36:40.787474       1 controller.go:185] kmm "msg"="Starting EventSource" "controller"="PreflightValidation" "controllerGroup"="kmm.sigs.x-k8s.io" "controllerKind"="PreflightValidation" "source"="kind source: *v1.Job"
    I0228 09:36:40.787488       1 controller.go:185] kmm "msg"="Starting EventSource" "controller"="PreflightValidation" "controllerGroup"="kmm.sigs.x-k8s.io" "controllerKind"="PreflightValidation" "source"="kind source: *v1beta1.Module"
    I0228 09:36:40.787603       1 controller.go:185] kmm "msg"="Starting EventSource" "controller"="NodeKernel" "controllerGroup"="" "controllerKind"="Node" "source"="kind source: *v1.Node"
    I0228 09:36:40.787634       1 controller.go:193] kmm "msg"="Starting Controller" "controller"="NodeKernel" "controllerGroup"="" "controllerKind"="Node"
    I0228 09:36:40.787680       1 controller.go:193] kmm "msg"="Starting Controller" "controller"="PreflightValidation" "controllerGroup"="kmm.sigs.x-k8s.io" "controllerKind"="PreflightValidation"
    I0228 09:36:40.785607       1 controller.go:185] kmm "msg"="Starting EventSource" "controller"="imagestream" "controllerGroup"="image.openshift.io" "controllerKind"="ImageStream" "source"="kind source: *v1.ImageStream"
    I0228 09:36:40.787822       1 controller.go:185] kmm "msg"="Starting EventSource" "controller"="preflightvalidationocp" "controllerGroup"="kmm.sigs.x-k8s.io" "controllerKind"="PreflightValidationOCP" "source"="kind source: *v1beta1.PreflightValidationOCP"
    I0228 09:36:40.787853       1 controller.go:193] kmm "msg"="Starting Controller" "controller"="imagestream" "controllerGroup"="image.openshift.io" "controllerKind"="ImageStream"
    I0228 09:36:40.787879       1 controller.go:185] kmm "msg"="Starting EventSource" "controller"="preflightvalidationocp" "controllerGroup"="kmm.sigs.x-k8s.io" "controllerKind"="PreflightValidationOCP" "source"="kind source: *v1beta1.PreflightValidation"
    I0228 09:36:40.787905       1 controller.go:193] kmm "msg"="Starting Controller" "controller"="preflightvalidationocp" "controllerGroup"="kmm.sigs.x-k8s.io" "controllerKind"="PreflightValidationOCP"
    I0228 09:36:40.786489       1 controller.go:193] kmm "msg"="Starting Controller" "controller"="PodNodeModule" "controllerGroup"="" "controllerKind"="Pod"

收集KMM-Hub数据

步骤
  1. 收集KMM Operator Hub控制器管理器的数据

    1. 设置MUST_GATHER_IMAGE变量

      $ export MUST_GATHER_IMAGE=$(oc get deployment -n openshift-kmm-hub kmm-operator-hub-controller -ojsonpath='{.spec.template.spec.containers[?(@.name=="manager")].env[?(@.name=="RELATED_IMAGE_MUST_GATHER")].value}')
      $ oc adm must-gather --image="${MUST_GATHER_IMAGE}" -- /usr/bin/gather -u

      如果您在自定义命名空间中安装了KMM,请使用-n <namespace>开关指定命名空间。

    2. 运行must-gather工具

      $ oc adm must-gather --image="${MUST_GATHER_IMAGE}" -- /usr/bin/gather -u
  2. 查看Operator日志

    $ oc logs -fn openshift-kmm-hub deployments/kmm-operator-hub-controller
    示例输出
    I0417 11:34:08.807472       1 request.go:682] Waited for 1.023403273s due to client-side throttling, not priority and fairness, request: GET:https://172.30.0.1:443/apis/tuned.openshift.io/v1?timeout=32s
    I0417 11:34:12.373413       1 listener.go:44] kmm-hub/controller-runtime/metrics "msg"="Metrics server is starting to listen" "addr"="127.0.0.1:8080"
    I0417 11:34:12.376253       1 main.go:150] kmm-hub/setup "msg"="Adding controller" "name"="ManagedClusterModule"
    I0417 11:34:12.376621       1 main.go:186] kmm-hub/setup "msg"="starting manager"
    I0417 11:34:12.377690       1 leaderelection.go:248] attempting to acquire leader lease openshift-kmm-hub/kmm-hub.sigs.x-k8s.io...
    I0417 11:34:12.378078       1 internal.go:366] kmm-hub "msg"="Starting server" "addr"={"IP":"127.0.0.1","Port":8080,"Zone":""} "kind"="metrics" "path"="/metrics"
    I0417 11:34:12.378222       1 internal.go:366] kmm-hub "msg"="Starting server" "addr"={"IP":"::","Port":8081,"Zone":""} "kind"="health probe"
    I0417 11:34:12.395703       1 leaderelection.go:258] successfully acquired lease openshift-kmm-hub/kmm-hub.sigs.x-k8s.io
    I0417 11:34:12.396334       1 controller.go:185] kmm-hub "msg"="Starting EventSource" "controller"="ManagedClusterModule" "controllerGroup"="hub.kmm.sigs.x-k8s.io" "controllerKind"="ManagedClusterModule" "source"="kind source: *v1beta1.ManagedClusterModule"
    I0417 11:34:12.396403       1 controller.go:185] kmm-hub "msg"="Starting EventSource" "controller"="ManagedClusterModule" "controllerGroup"="hub.kmm.sigs.x-k8s.io" "controllerKind"="ManagedClusterModule" "source"="kind source: *v1.ManifestWork"
    I0417 11:34:12.396430       1 controller.go:185] kmm-hub "msg"="Starting EventSource" "controller"="ManagedClusterModule" "controllerGroup"="hub.kmm.sigs.x-k8s.io" "controllerKind"="ManagedClusterModule" "source"="kind source: *v1.Build"
    I0417 11:34:12.396469       1 controller.go:185] kmm-hub "msg"="Starting EventSource" "controller"="ManagedClusterModule" "controllerGroup"="hub.kmm.sigs.x-k8s.io" "controllerKind"="ManagedClusterModule" "source"="kind source: *v1.Job"
    I0417 11:34:12.396522       1 controller.go:185] kmm-hub "msg"="Starting EventSource" "controller"="ManagedClusterModule" "controllerGroup"="hub.kmm.sigs.x-k8s.io" "controllerKind"="ManagedClusterModule" "source"="kind source: *v1.ManagedCluster"
    I0417 11:34:12.396543       1 controller.go:193] kmm-hub "msg"="Starting Controller" "controller"="ManagedClusterModule" "controllerGroup"="hub.kmm.sigs.x-k8s.io" "controllerKind"="ManagedClusterModule"
    I0417 11:34:12.397175       1 controller.go:185] kmm-hub "msg"="Starting EventSource" "controller"="imagestream" "controllerGroup"="image.openshift.io" "controllerKind"="ImageStream" "source"="kind source: *v1.ImageStream"
    I0417 11:34:12.397221       1 controller.go:193] kmm-hub "msg"="Starting Controller" "controller"="imagestream" "controllerGroup"="image.openshift.io" "controllerKind"="ImageStream"
    I0417 11:34:12.498335       1 filter.go:196] kmm-hub "msg"="Listing all ManagedClusterModules" "managedcluster"="local-cluster"
    I0417 11:34:12.498570       1 filter.go:205] kmm-hub "msg"="Listed ManagedClusterModules" "count"=0 "managedcluster"="local-cluster"
    I0417 11:34:12.498629       1 filter.go:238] kmm-hub "msg"="Adding reconciliation requests" "count"=0 "managedcluster"="local-cluster"
    I0417 11:34:12.498687       1 filter.go:196] kmm-hub "msg"="Listing all ManagedClusterModules" "managedcluster"="sno1-0"
    I0417 11:34:12.498750       1 filter.go:205] kmm-hub "msg"="Listed ManagedClusterModules" "count"=0 "managedcluster"="sno1-0"
    I0417 11:34:12.498801       1 filter.go:238] kmm-hub "msg"="Adding reconciliation requests" "count"=0 "managedcluster"="sno1-0"
    I0417 11:34:12.501947       1 controller.go:227] kmm-hub "msg"="Starting workers" "controller"="imagestream" "controllerGroup"="image.openshift.io" "controllerKind"="ImageStream" "worker count"=1
    I0417 11:34:12.501948       1 controller.go:227] kmm-hub "msg"="Starting workers" "controller"="ManagedClusterModule" "controllerGroup"="hub.kmm.sigs.x-k8s.io" "controllerKind"="ManagedClusterModule" "worker count"=1
    I0417 11:34:12.502285       1 imagestream_reconciler.go:50] kmm-hub "msg"="registered imagestream info mapping" "ImageStream"={"name":"driver-toolkit","namespace":"openshift"} "controller"="imagestream" "controllerGroup"="image.openshift.io" "controllerKind"="ImageStream" "dtkImage"="quay.io/openshift-release-dev/ocp-v4.0-art-dev@sha256:df42b4785a7a662b30da53bdb0d206120cf4d24b45674227b16051ba4b7c3934" "name"="driver-toolkit" "namespace"="openshift" "osImageVersion"="412.86.202302211547-0" "reconcileID"="e709ff0a-5664-4007-8270-49b5dff8bae9"