Showing posts with label Netapp. Show all posts
Showing posts with label Netapp. Show all posts

Friday, July 11, 2025

NVIDIA GPU Direct Storage on OpenShift

GPU Direct Storage enables a direct data path for direct memory access (DMA) transfers between GPU memory and storage, which avoids a bounce buffer through the CPU. Using this direct path can relieve system bandwidth bottlenecks and decrease the latency and utilization load on the CPU. GPU Direct Storage can be used with NVMe or even NFS on a Netapp filer, the latter which this blog will cover.

Workflow

This blog is laid out with the follow sections all which build on top of one another to get the goal of successful GPU Direct Storage over NFS.

  • Assumptions
  • Considerations
  • Architecture
  • SRIOV Operator Configuration
  • Netapp VServer Setup
  • Netapp Trident CSI Operator Configuration
  • NVIDIA Network Operator Configuration
  • NVIDIA GPU Operator Configuration
  • GDS Cuda Workload Container

Assumptions

This document assumes that we have already deployed a OpenShift Cluster and have installed the necessary operators required for GPU Direct Storage. Those operators would be Node Feature Discover which should also be configured along with the base installation of the NVIDIA Network Operator (no NicClusterPolicy yet) and the NVIDIA GPU Operator (no GpuClusterPolicy yet), SRIOV Operator (no SRIOV policies or instances) and the Trident CSI Operator (No orchestrators or backends configured yet).

Considerations

If any of the nvme devices in the system participate in either the operating system or other services (machine configs for LVMs or other customized access) the nvme kernel modules will not be able to unload properly even with the workaround defined in this documentation. Any use of GDS requires that the nvme drives are not in use during the deployment of the Network Operator in order for the Network Operator to be able to unload in-tree drivers and then load NVIDIA's out of tree drivers in place.

Architecture

Below is a diagram of how the environment was architected from a networking perspective.

SRIOV Operator Configuration

For GPU Direct Storage over NFS to make performance sense we will need to use SRIOV here. So we first need to configure the SRIOV Operator assuming the SRIOV Operator is installed. The first step is to generate a basic SriovOperatorConfig custom resource file.

$ cat <<EOF > sriov-operator-config.yaml apiVersion: sriovnetwork.openshift.io/v1 kind: SriovOperatorConfig metadata: name: default namespace: openshift-sriov-network-operator spec: enableInjector: true enableOperatorWebhook: true logLevel: 2 EOF

Next we create the SriovOperatorConfig on the cluster.

$ oc create -f sriov-operator-config.yaml sriovoperatorconfig.sriovnetwork.openshift.io/default created

Now one key step here is to patch the SriovOperatorConfig so that it is aware of the NVIDIA Network Operator.

$ oc patch sriovoperatorconfig default --type=merge -n openshift-sriov-network-operator --patch '{ "spec": { "configDaemonNodeSelector": { "network.nvidia.com/operator.mofed.wait": "false", "node-role.kubernetes.io/worker": "", "feature.node.kubernetes.io/pci-15b3.sriov.capable": "true" } } }' sriovoperatorconfig.sriovnetwork.openshift.io/default patched

Now we can move onto generating a SriovNetworkNodePolicy which will define the interface that we want to have VFs. In the case of multiple interfaces we would want to create multiple SriovNetworkNodePolicy files. The example below demonstrates how to configure an interface with an MTU of 9000 and generate 8 VFs.

$ cat <<EOF > sriov-network-node-policy.yaml apiVersion: sriovnetwork.openshift.io/v1 kind: SriovNetworkNodePolicy metadata: name: sriov-legacy-policy namespace: openshift-sriov-network-operator spec: deviceType: netdevice mtu: 9000 nicSelector: vendor: "15b3" pfNames: ["enp55s0np0#0-7"] nodeSelector: feature.node.kubernetes.io/pci-15b3.present: "true" numVfs: 8 priority: 90 isRdma: true resourceName: sriovlegacy EOF

With the SriovNetworkNodePolicy generated we can create it on the cluster which will cause the worker nodes where it is applied to reboot.

$ oc create -f sriov-network-node-policy.yaml sriovnetworknodepolicy.sriovnetwork.openshift.io/sriov-legacy-policy created

Once the node has rebooted we can optionally open a debug pod on the worker nodes and verify with ip link to confirm the interfaces were created. If we are ready to move forward we can next generate the SriovNetwork for the resource we created in the SriovNetworkNodePolicy. Again if we have multiple SriovNetworkNodePolicy files we will also have multiple SriovNetwork files. These define the network space for the VF interfaces. I should note that these networks need to have access to the Netapp data LIF as well in order for RDMA to function. In my example below I excluded the ipaddresses in range of 102.168.10.100-110 because my Netapp data LIF will have ipaddresss in that space.

$ cat <<EOF > sriov-network.yaml apiVersion: sriovnetwork.openshift.io/v1 kind: SriovNetwork metadata: name: sriov-network namespace: openshift-sriov-network-operator spec: vlan: 0 networkNamespace: "default" resourceName: "sriovlegacy" ipam: | { "type": "whereabouts", "range": "192.168.10.0/24", "exclude": [ "192.168.10.100/30", "192.168.10.110/32" ] } EOF

Now we can create the SriovNetwork custom resource on the cluster.

$ oc create -f sriov-network.yaml sriovnetwork.sriovnetwork.openshift.io/sriov-network created

At this point we have configured everything we need for SRIOV and can move onto the next section of the documentation.

Netapp VServer Setup

This section is really just to cover a few items of importance from the Netapp vserver perspective. This does not aim to be a comprehensive guide on how to setup a Netapp MetroCluster or the vservers within them. First in our example environment we had a vserver created and that vserver as two logical interfaces: management and data. With the management interface we can access the vserver and look at a few things. Depending on the environment this may or may not be accessible for the OpenShift administrator. In my case the storage team gave me access. To get on the vserver we can ssh to the vserver ipaddress or fqdn if it exists in DNS.

$ ssh trident@10.6.136.110 (trident@10.6.136.110) Password: Last login time: 5/7/2025 19:31:11

Once we are logged in I want to confirm that NFS 4 is enabled along with RDMA by using vserver nfs show.

ntap-rdu3-nv01-nvidia::> vserver nfs show Vserver: ntap-rdu3-nv01-nvidia General Access: true v3: enabled v4.0: enabled 4.1: enabled UDP: enabled TCP: enabled RDMA: enabled Default Windows User: - Default Windows Group: -

The above output looks good for my needs when doing GPU Direct Storage. Another item we can check is the export-policies with vserver export-policy show.

ntap-rdu3-nv01-nvidia::> vserver export-policy show Vserver Policy Name --------------- ------------------- ntap-rdu3-nv01-nvidia default ntap-rdu3-nv01-nvidia trident-8d6b2406-551a-416b-bcce-22626ed60242 2 entries were displayed.

And finally I wanted to confirm that my data interfaces connected to the NVIDIA high speed switch were indeed operating with jumbo frames. I can see that with the network port show command. Because this is a MetroCluster pair setup we can see the interfaces on both nodes is set appropriately.

ntap-rdu3-nv01-nvidia::> network port show Node: ntap-rdu3-nv01-a Speed(Mbps) Health Port Broadcast Domain Link MTU Admin/Oper Status --------- ------------ ---------------- ---- ---- ----------- -------- e0M Management up 1500 auto/1000 healthy e1b - down 1500 auto/- - e2a nvidia up 9000 auto/200000 healthy e2b - up 1500 auto/100000 healthy e2b-710 nfs up 1500 -/- healthy e6a - down 1500 auto/- - e6b - down 1500 auto/- - e7b - down 1500 auto/- - e8a - down 1500 auto/- - e8b - down 1500 auto/- - Node: ntap-rdu3-nv01-b Speed(Mbps) Health Port Broadcast Domain Link MTU Admin/Oper Status --------- ------------ ---------------- ---- ---- ----------- -------- e0M Management up 1500 auto/1000 healthy e1b - down 1500 auto/- - e2a nvidia up 9000 auto/200000 healthy e2b - up 1500 auto/100000 healthy e2b-710 nfs up 1500 -/- healthy e6a - down 1500 auto/- - e6b - down 1500 auto/- - e7b - down 1500 auto/- - e8a - down 1500 auto/- - e8b - down 1500 auto/- - 20 entries were displayed.

At this point we can exit out of the vserver and move onto configuring the Netapp Trident CSI operator.

Netapp Trident CSI Operator Configuration

Trident is an open-source and fully supported storage orchestrator for containers and Kubernetes distributions, including Red Hat OpenShift. Trident works with the entire NetApp storage portfolio, including the NetApp ONTAP and Element storage systems, and it also supports NFS and iSCSI connections. Trident accelerates the DevOps workflow by allowing end users to provision and manage storage from their NetApp storage systems without requiring intervention from a storage administrator.

We have made the assumption that the Trident Operator and the default Trident Orchestrator have already been deployed.  Our next step will be to configure the secret for the Netapp vfiler with the credentials so that Trident knows how which username and password to connect. 

$ cat <<EOF > netapp-phy-secret.yaml apiVersion: v1 kind: Secret metadata: name: netapp-phy-secret namespace: trident type: Opaque stringData: username: vserver-user password: verserv-password
Once we have our custom resource file generated we can create it on the cluster.
$ oc create -f netapp-phy-secret.yaml secret/netapp-phy-secret created
Next we need to configure the TridentBackendConfig so that Trident knows how to communicate with the Netapp from both a management and data perspective.  Note the credentials we created are referenced here.
$ cat <<EOF > netapp-phy-tridentbackendconfig.yaml apiVersion: trident.netapp.io/v1 kind: TridentBackendConfig metadata: name: netapp-phy-nfs-backend namespace: trident spec: version: 1 storageDriverName: ontap-nas-flexgroup managementLIF: 10.6.136.110 dataLIF: 192.168.10.101 backendName: phy-nfs-backend svm: ntap-rdu3-nv01-nvidia autoExportPolicy: true credentials: name: netapp-phy-secret
With the custom resource file generated we can create it on the cluster.
$ oc create -f netapp-phy-tridentbackendconfig.yaml tridentbackendconfig.trident.netapp.io/netapp-phy-nfs-backend created
We can validate the backend is there with the follow check.
$ oc get tridentbackend -n trident NAME BACKEND BACKEND UUID tbe-n59xq phy-nfs-backend 8d6b2406-551a-416b-bcce-22626ed60242
We can also describe the backend as well.
$ oc describe tridentbackend tbe-n59xq -n trident Name: tbe-n59xq Namespace: trident Labels: <none> Annotations: <none> API Version: trident.netapp.io/v1 Backend Name: phy-nfs-backend Backend UUID: 8d6b2406-551a-416b-bcce-22626ed60242 Config: ontap_config: Aggregate: Auto Export CID Rs: 0.0.0.0/0 ::/0 Auto Export Policy: true Backend Name: phy-nfs-backend Backend Pools: eyJzdm1VVUlEIjoiNjE2OTg1YTYtMjlkZi0xMWYwLWI4YzctZDAzOWVhYzA0MDUzIn0= Chap Initiator Secret: Chap Target Initiator Secret: Chap Target Username: Chap Username: Client Certificate: Client Private Key: Clone Split Delay: 10 Credentials: Name: netapp-phy-secret Data LIF: 192.168.10.101 Debug: false Debug Trace Flags: <nil> Defaults: LUKS Encryption: false Adaptive Qos Policy: Encryption: Export Policy: <automatic> File System Type: ext4 Format Options: Mirroring: false Name Template: Qos Policy: Security Style: unix Size: 1G Skip Recovery Queue: false Snapshot Dir: false Snapshot Policy: none Snapshot Reserve: Space Allocation: true Space Reserve: none Split On Clone: false Tiering Policy: Unix Permissions: ---rwxrwxrwx Deny New Volume Pools: false Disable Delete: false Empty Flexvol Deferred Delete Period: Flags: Disaggregated: false Personality: Unified San Optimized: false Flexgroup Aggregate List: Igroup Name: Labels: <nil> Limit Aggregate Usage: Limit Volume Pool Size: Limit Volume Size: Luns Per Flexvol: Management LIF: 10.6.136.110 Nas Type: nfs Nfs Mount Options: Password: secret:netapp-phy-secret Qtree Prune Flexvols Period: Qtree Quota Resize Period: Qtrees Per Flexvol: Region: Replication Policy: Replication Schedule: San Type: iscsi Smb Share: Storage: <nil> Storage Driver Name: ontap-nas-flexgroup Storage Prefix: Supported Topologies: <nil> Svm: ntap-rdu3-nv01-nvidia Trusted CA Certificate: Usage Heartbeat: Use CHAP: false Use REST: <nil> User State: Username: secret:netapp-phy-secret Version: 1 Zone: Config Ref: 9e1ff3f2-8a2d-4efa-859c-712b920d269b Kind: TridentBackend Metadata: Creation Timestamp: 2025-05-07T19:31:56Z Finalizers: trident.netapp.io Generate Name: tbe- Generation: 1 Resource Version: 38713504 UID: 6536970f-b10e-4e04-8a37-8da56deaf69e Online: true State: online User State: normal Version: 1 Events: <none>
We can also use the tridentctl command to validate the backend and confirm its online.
$ ./trident-installer/tridentctl get backend -n trident +-----------------+---------------------+--------------------------------------+--------+------------+---------+ | NAME | STORAGE DRIVER | UUID | STATE | USER-STATE | VOLUMES | +-----------------+---------------------+--------------------------------------+--------+------------+---------+ | phy-nfs-backend | ontap-nas-flexgroup | 8d6b2406-551a-416b-bcce-22626ed60242 | online | normal | 0 | +-----------------+---------------------+--------------------------------------+--------+------------+---------+
With the Trident backend configured we can move onto generating a storageclass resource file.  Note while this looks just like a standard Trident NFS storageclass the designation of the rdma makes it special.
$ cat <<EOF > netapp-phy-rdma-storageclass.yaml apiVersion: storage.k8s.io/v1 kind: StorageClass metadata: name: netapp-phy-nfs provisioner: csi.trident.netapp.io parameters: backendType: "ontap-nas-flexgroup" mountOptions: - vers=4.1 - proto=rdma - max_connect=16 - rsize=262144 - wsize=262144 - write=eager EOF
Once we have generated the custom resource file we can create it on the cluster.
$ oc create -f netapp-phy-rdma-storageclass.yaml storageclass.storage.k8s.io/netapp-phy-nfs created
We can validate the storageclass by looking at the storage classes available.
$ oc get sc NAME PROVISIONER RECLAIMPOLICY VOLUMEBINDINGMODE ALLOWVOLUMEEXPANSION AGE netapp-phy-nfs csi.trident.netapp.io Delete Immediate false 4s
Now with the storagclass configured we can generate a persistent volume resource file.
$ cat <<EOF > netapp-phy-pvc.yaml kind: PersistentVolumeClaim apiVersion: v1 metadata: name: pvc-netapp-phy-test spec: accessModes: - ReadWriteOnce resources: requests: storage: 850Gi storageClassName: netapp-phy-nfs EOF
We can take the persistent volume resource and create it on the cluster.
$ oc create -f netapp-phy-pvc.yaml persistentvolumeclaim/pvc-netapp-phy-test created
We can validate the persistent volume by looking at the pvc.
$ oc get pvc NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS VOLUMEATTRIBUTESCLASS AGE pvc-netapp-phy-test Bound pvc-ae477c5c-cf10-4bc0-bb71-39d214a237f0 850Gi RWO netapp-phy-nfs <unset> 45s

At this point we have completed the setup of the Trident storage side in preparation for GPU Direct Storage.

NVIDIA Network Operator Configuration

We assume the Network Operator has already been installed on the cluster but the NicClusterPolicy still needs to be created. The following NicClusterPolicy example will provide the needed configuration to ensure RDMA is properly loaded for NFS. The key option in this policy is the ENABLE_NFSRDMA variable and having it set to true. I want to note that this policy also optinonally has an rdmaSharedDevice and ENTRYPOINT_DEBUG set to true for more verbose logging.

$ cat <<EOF > network-sriovleg-nic-cluster-policy.yaml apiVersion: mellanox.com/v1alpha1 kind: NicClusterPolicy metadata: name: nic-cluster-policy spec: ofedDriver: image: doca-driver repository: nvcr.io/nvidia/mellanox version: 25.01-0.6.0.0-0 startupProbe: initialDelaySeconds: 10 periodSeconds: 20 livenessProbe: initialDelaySeconds: 30 periodSeconds: 30 readinessProbe: initialDelaySeconds: 10 periodSeconds: 30 env: - name: UNLOAD_STORAGE_MODULES value: "true" - name: RESTORE_DRIVER_ON_POD_TERMINATION value: "true" - name: CREATE_IFNAMES_UDEV value: "true" - name: ENABLE_NFSRDMA value: "true" - name: ENTRYPOINT_DEBUG value: 'true' EOF

Before creating the NicClusterPolicy on the cluster we need to prepare a script which will allow us to workaround an issue with GPU Direct Storage in the NVIDIA Network Operator. This script when run right after creating the NicClusterPolicy will determine which nodes have mofed pods running on them and based on that node list will ssh as the core user into each node and unload the following modules: nvme, nvme_tcp, nvme_fabrics, nvme_core. By using the script to unload the modules while the mofed container is busying building the doca drivers we eliminate an issue where when the mofed container goes to install the compiled doca drivers there is a failure to load. One might ask what does NVMe have to do with NFS and unfortunately GPU Direct Storage enablement does both so we have to work around this issue.

$ cat <<EOF > nvme-fixer.sh #!/bin/bash ### Set array of modules to be unloaded declare -a modarr=("nvme" "nvme_tcp" "nvme_fabrics" "nvme_core") ### Determine which hosts have mofed container running on them declare -a hostarr=(`oc get pods -n nvidia-network-operator -o custom-columns=POD:.metadata.name,NODE:.spec..nodeName --no-headers|grep mofed|awk {'print $2'}`) ### Iterate through modules on each host and unload them for host in "${hostarr[@]}" do echo "Unloading nvme dependencies on $host..." for module in "${modarr[@]}" do echo "Unloading module $module..." ssh core@$host sudo rmmod $module done done EOF

Change the execute bit on the file.

$ chmod +x nvme-fixer.sh

Now we are ready to create the NicClusterPolicy on the cluster and follow it up by running the nvme-fixer.sh script. If there are any rmmod errors those can safely be ignored as the module was not loaded to start with. In the example below we had two workers nodes that had mofed pods running on them so the script went ahead and unloaded the nvme modules.

$ oc create -f network-sharedrdma-nic-cluster-policy.yaml nicclusterpolicy.mellanox.com/nic-cluster-policy created $ ./nvme-fixer.sh Unloading nvme dependencies on nvd-srv-30.nvidia.eng.rdu2.dc.redhat.com... Unloading module nvme... Unloading module nvme_tcp... rmmod: ERROR: Module nvme_tcp is not currently loaded Unloading module nvme_fabrics... rmmod: ERROR: Module nvme_fabrics is not currently loaded Unloading module nvme_core... Unloading nvme dependencies on nvd-srv-29.nvidia.eng.rdu2.dc.redhat.com... Unloading module nvme... Unloading module nvme_tcp... Unloading module nvme_fabrics... Unloading module nvme_core...

Now we wait for the mofed pod to finish compiling and installed the GPU Direct Storage modules. We will know its complete when the pods are in a running state like below:

$ oc get pods -n nvidia-network-operator NAME READY STATUS RESTARTS AGE mofed-rhcos4.16-56c9d799bf-ds-bvhmj 2/2 Running 0 20h mofed-rhcos4.16-56c9d799bf-ds-jdzxj 2/2 Running 0 20h nvidia-network-operator-controller-manager-85b78c49f6-9lchx 1/1 Running 4 (3h26m ago) 3d14h

This completes the NVIDIA Network Operator portion of the configuration for GPU Direct Storage.

NVIDIA GPU Operator Configuration

Now that the NicClusterPolicy is defined and the proper nvme modules have been loaded we can move onto configuring our GPU ClusterPolicy. The below example is a policy that will enable GPU Direct Storage on the worker nodes that have a proper NVIDIA GPU.

$ cat <<EOF > gpu-cluster-policy.yaml apiVersion: nvidia.com/v1 kind: ClusterPolicy metadata: name: gpu-cluster-policy spec: vgpuDeviceManager: config: default: default enabled: true migManager: config: default: all-disabled name: default-mig-parted-config enabled: true operator: defaultRuntime: crio initContainer: {} runtimeClass: nvidia use_ocp_driver_toolkit: true dcgm: enabled: true gfd: enabled: true dcgmExporter: config: name: '' enabled: true serviceMonitor: enabled: true cdi: default: false enabled: false driver: licensingConfig: configMapName: '' nlsEnabled: true enabled: true kernelModuleType: open certConfig: name: '' useNvidiaDriverCRD: false kernelModuleConfig: name: '' upgradePolicy: autoUpgrade: true drain: deleteEmptyDir: false enable: false force: false timeoutSeconds: 300 maxParallelUpgrades: 1 maxUnavailable: 25% podDeletion: deleteEmptyDir: false force: false timeoutSeconds: 300 waitForCompletion: timeoutSeconds: 0 repoConfig: configMapName: '' virtualTopology: config: '' devicePlugin: config: default: '' name: '' enabled: true mps: root: /run/nvidia/mps gdrcopy: enabled: true kataManager: config: artifactsDir: /opt/nvidia-gpu-operator/artifacts/runtimeclasses mig: strategy: single sandboxDevicePlugin: enabled: true validator: plugin: env: - name: WITH_WORKLOAD value: 'false' nodeStatusExporter: enabled: true daemonsets: rollingUpdate: maxUnavailable: '1' updateStrategy: RollingUpdate sandboxWorkloads: defaultWorkload: container enabled: false gds: enabled: true image: nvidia-fs repository: nvcr.io/nvidia/cloud-native version: 2.25.7 vgpuManager: enabled: false vfioManager: enabled: true toolkit: enabled: true installDir: /usr/local/nvidia EOF

Now let's create the policy on the cluster.

$ oc create -f gpu-cluster-policy.yaml clusterpolicy.nvidia.com/gpu-cluster-policy created

Once the policy is created let's validate the pods are running before we move onto the next step.

$ oc get pods -n nvidia-gpu-operator NAME READY STATUS RESTARTS AGE gpu-feature-discovery-nttht 1/1 Running 0 20h gpu-feature-discovery-r4ktv 1/1 Running 0 20h gpu-operator-7d7f694bfb-957mv 1/1 Running 0 20h nvidia-container-toolkit-daemonset-h96t6 1/1 Running 0 20h nvidia-container-toolkit-daemonset-hqtrl 1/1 Running 0 20h nvidia-cuda-validator-66ml7 0/1 Completed 0 20h nvidia-dcgm-exporter-hbk4r 1/1 Running 0 20h nvidia-dcgm-exporter-pgh4q 1/1 Running 0 20h nvidia-dcgm-nttds 1/1 Running 0 20h nvidia-dcgm-zb4fl 1/1 Running 0 20h nvidia-device-plugin-daemonset-d99md 1/1 Running 0 20h nvidia-device-plugin-daemonset-w7tc4 1/1 Running 0 20h nvidia-driver-daemonset-416.94.202504151456-0-8bdl5 4/4 Running 26 (20h ago) 2d2h nvidia-driver-daemonset-416.94.202504151456-0-j8gps 4/4 Running 20 (20h ago) 2d2h nvidia-node-status-exporter-b22hk 1/1 Running 4 2d2h nvidia-node-status-exporter-lwqhb 1/1 Running 3 2d2h nvidia-operator-validator-cvqn5 1/1 Running 0 20h nvidia-operator-validator-zxrpb 1/1 Running 0 20h

With the NVIDIA GPU Operator pods running we can rsh into the daemonset pods and confirm GDS is enabled by running the lsmod command and cat out the /proc/driver/nvidia-fs/stats file.

$ oc rsh -n nvidia-gpu-operator nvidia-driver-daemonset-416.94.202504151456-0-8bdl5 sh-4.4# lsmod|grep nvidia nvidia_fs 327680 0 nvidia_modeset 1720320 0 video 73728 1 nvidia_modeset nvidia_uvm 4087808 12 nvidia 11665408 36 nvidia_uvm,nvidia_fs,gdrdrv,nvidia_modeset drm 741376 5 drm_kms_helper,drm_shmem_helper,nvidia,mgag200 sh-4.4# cat /proc/driver/nvidia-fs/stats GDS Version: 1.10.0.4 NVFS statistics(ver: 4.0) NVFS Driver(version: 2.20.5) Mellanox PeerDirect Supported: False IO stats: Disabled, peer IO stats: Disabled Logging level: info Active Shadow-Buffer (MiB): 0 Active Process: 0 Reads : err=0 io_state_err=0 Sparse Reads : n=0 io=0 holes=0 pages=0 Writes : err=0 io_state_err=0 pg-cache=0 pg-cache-fail=0 pg-cache-eio=0 Mmap : n=0 ok=0 err=0 munmap=0 Bar1-map : n=0 ok=0 err=0 free=0 callbacks=0 active=0 delay-frees=0 Error : cpu-gpu-pages=0 sg-ext=0 dma-map=0 dma-ref=0 Ops : Read=0 Write=0 BatchIO=0

If everything looks good we can move onto an additional step to confirm GDS is ready for workload consumption.

GDS Cuda Workload Container

Once the GPU Direct Storage drivers are loaded we can use one more additional tool to check and confirm GDS capability. This involves building a container that contains the CUDA packages and then running it on a node.

Now let's generate a service account CRD to use in the default namespace.

$ cat <<EOF > nvidiatools-serviceaccount.yaml apiVersion: v1 kind: ServiceAccount metadata: name: nvidiatools namespace: default EOF

Next we can create it on our cluster.

$ oc create -f default-serviceaccount.yaml serviceaccount/rdma created

Finally with the service account create we can add privleges to it.

$ oc -n default adm policy add-scc-to-user privileged -z nvidiatools clusterrole.rbac.authorization.k8s.io/system:openshift:scc:privileged added: "nvidiatools"

With the service account defined and our pod yaml ready we can create it on the cluster.

The following pod yaml defines this configuration.

$ cat <<EOF > nvidiatools-30-workload.yaml apiVersion: v1 kind: Pod metadata: name: nvidiatools-30-workload namespace: default annotations: # JSON list is the canonical form; adjust if your NAD lives in another namespace k8s.v1.cni.cncf.io/networks: '[{ "name": "sriov-network" }]' spec: serviceAccountName: nvidiatools nodeSelector: kubernetes.io/hostname: nvd-srv-30.nvidia.eng.rdu2.dc.redhat.com volumes: - name: rdma-pv-storage persistentVolumeClaim: claimName: pvc-netapp-phy-test - name: nordma-pv-storage persistentVolumeClaim: claimName: pvc-netapp-phy-nordma-test containers: - name: nvidiatools-30-workload image: quay.io/redhat_emp1/ecosys-nvidia/nvidia-tools:0.0.3 imagePullPolicy: IfNotPresent securityContext: privileged: true capabilities: add: ["IPC_LOCK"] resources: limits: nvidia.com/gpu: 1 openshift.io/sriovlegacy: 1 requests: nvidia.com/gpu: 1 openshift.io/sriovlegacy: 1 volumeMounts: - name: rdma-pv-storage mountPath: /nfsfast - name: nordma-pv-storage mountPath: /nfsslow EOF
$ oc create -f nvidiatools-30-workload.yaml nvidiatools-30-workload created $ oc get pods NAME READY STATUS RESTARTS AGE nvidiatools-30-workload 1/1 Running 0 3s

Once the pod is up and running we can rsh into the pod and run the gdscheck tool to confirm capabilities and configuration of GPU Direct Storage.

$ oc rsh nvidiatools-30-workload sh-5.1# /usr/local/cuda/gds/tools/gdscheck -p GDS release version: 1.13.1.3 nvidia_fs version: 2.20 libcufile version: 2.12 Platform: x86_64 ============ ENVIRONMENT: ============ ===================== DRIVER CONFIGURATION: ===================== NVMe P2PDMA : Unsupported NVMe : Supported NVMeOF : Supported SCSI : Unsupported ScaleFlux CSD : Unsupported NVMesh : Unsupported DDN EXAScaler : Unsupported IBM Spectrum Scale : Unsupported NFS : Supported BeeGFS : Unsupported WekaFS : Unsupported Userspace RDMA : Unsupported --Mellanox PeerDirect : Disabled --rdma library : Not Loaded (libcufile_rdma.so) --rdma devices : Not configured --rdma_device_status : Up: 0 Down: 0 ===================== CUFILE CONFIGURATION: ===================== properties.use_pci_p2pdma : false properties.use_compat_mode : true properties.force_compat_mode : false properties.gds_rdma_write_support : true properties.use_poll_mode : false properties.poll_mode_max_size_kb : 4 properties.max_batch_io_size : 128 properties.max_batch_io_timeout_msecs : 5 properties.max_direct_io_size_kb : 16384 properties.max_device_cache_size_kb : 131072 properties.max_device_pinned_mem_size_kb : 33554432 properties.posix_pool_slab_size_kb : 4 1024 16384 properties.posix_pool_slab_count : 128 64 64 properties.rdma_peer_affinity_policy : RoundRobin properties.rdma_dynamic_routing : 0 fs.generic.posix_unaligned_writes : false fs.lustre.posix_gds_min_kb: 0 fs.beegfs.posix_gds_min_kb: 0 fs.weka.rdma_write_support: false fs.gpfs.gds_write_support: false fs.gpfs.gds_async_support: true profile.nvtx : false profile.cufile_stats : 0 miscellaneous.api_check_aggressive : false execution.max_io_threads : 4 execution.max_io_queue_depth : 128 execution.parallel_io : true execution.min_io_threshold_size_kb : 8192 execution.max_request_parallelism : 4 properties.force_odirect_mode : false properties.prefer_iouring : false ========= GPU INFO: ========= GPU index 0 NVIDIA L40S bar:1 bar size (MiB):65536 supports GDS, IOMMU State: Disabled ============== PLATFORM INFO: ============== IOMMU: disabled Nvidia Driver Info Status: Supported(Nvidia Open Driver Installed) Cuda Driver Version Installed: 12080 Platform: PowerEdge R760xa, Arch: x86_64(Linux 5.14.0-427.65.1.el9_4.x86_64) Platform verification succeeded

Now let's confirm our GPU Direct NFS mount is mounted. Notice in the output the proto is rdma.

sh-5.1# mount|grep nfs 192.168.10.101:/trident_pvc_ae477c5c_cf10_4bc0_bb71_39d214a237f0 on /mnt type nfs4 (rw,relatime,vers=4.1,rsize=262144,wsize=262144,namlen=255,hard,proto=rdma,max_connect=16,port=20049,timeo=600,retrans=2,sec=sys,clientaddr=192.168.10.30,local_lock=none,write=eager,addr=192.168.10.101)

Next we can use gdsio to run some benchmarks across the GPU Direct NFS mount. Before we run the benchmarks let's familiarize ourselves with the all the gdsio switches and what they mean.

sh-5.1# /usr/local/cuda-12.8/gds/tools/gdsio -h gdsio version :1.12 Usage [using config file]: gdsio rw-sample.gdsio Usage [using cmd line options]:/usr/local/cuda-12.8/gds/tools/gdsio -f <file name> -D <directory name> -d <gpu_index (refer nvidia-smi)> -n <numa node> -m <memory type(0 - (cudaMalloc), 1 - (cuMem), 2 - (cudaMallocHost), 3 - (malloc) 4 - (mmap))> -w <number of threads for a job> -s <file size(K|M|G)> -o <start offset(K|M|G)> -i <io_size(K|M|G)> <min_size:max_size:step_size> -p <enable nvlinks> -b <skip bufregister> -V <verify IO> -x <xfer_type> [0(GPU_DIRECT), 1(CPU_ONLY), 2(CPU_GPU), 3(CPU_ASYNC_GPU), 4(CPU_CACHED_GPU), 5(GPU_DIRECT_ASYNC), 6(GPU_BATCH), 7(GPU_BATCH_STREAM)] -B <batch size> -I <(read) 0|(write)1| (randread) 2| (randwrite) 3> -T <duration in seconds> -k <random_seed> (number e.g. 3456) to be used with random read/write> -U <use unaligned(4K) random offsets> -R <fill io buffer with random data> -F <refill io buffer with random data during each write> -a <alignment size in case of random IO> -M <mixed_rd_wr_percentage in case of regular batch mode> -P <rdma url> -J <per job statistics> xfer_type: 0 - Storage->GPU (GDS) 1 - Storage->CPU 2 - Storage->CPU->GPU 3 - Storage->CPU->GPU_ASYNC 4 - Storage->PAGE_CACHE->CPU->GPU 5 - Storage->GPU_ASYNC 6 - Storage->GPU_BATCH 7 - Storage->GPU_BATCH_STREAM Note: read test (-I 0) with verify option (-V) should be used with files written (-I 1) with -V option read test (-I 2) with verify option (-V) should be used with files written (-I 3) with -V option, using same random seed (-k), same number of threads(-w), offset(-o), and data size(-s) write test (-I 1/3) with verify option (-V) will perform writes followed by read

Before we begin running some tests I want to note that the tests are being run from a standard Dell R760xa and from the nvidia-smi topo output we can see we are dealing with a non optimal setup of NODE where the connection traversing PCIe as well as the interconnect between PCIe Host Bridges within a NUMA node. Ideally for peformant numbers we would want to run this on a H100 or B200 where the GPU and NIC are connected to the same PCIe switch and yield a PHB,PXB or PIX connection.

sh-5.1# nvidia-smi topo -mp GPU0 NIC0 NIC1 NIC2 NIC3 NIC4 NIC5 NIC6 NIC7 NIC8 NIC9 CPU Affinity NUMA Affinity GPU NUMA ID GPU0 X NODE NODE NODE NODE NODE NODE NODE NODE NODE NODE 0,2,4,6,8,10 0 N/A NIC0 NODE X NODE NODE NODE NODE NODE NODE NODE NODE NODE NIC1 NODE NODE X PIX PIX PIX PIX PIX PIX PIX PIX NIC2 NODE NODE PIX X PIX PIX PIX PIX PIX PIX PIX NIC3 NODE NODE PIX PIX X PIX PIX PIX PIX PIX PIX NIC4 NODE NODE PIX PIX PIX X PIX PIX PIX PIX PIX NIC5 NODE NODE PIX PIX PIX PIX X PIX PIX PIX PIX NIC6 NODE NODE PIX PIX PIX PIX PIX X PIX PIX PIX NIC7 NODE NODE PIX PIX PIX PIX PIX PIX X PIX PIX NIC8 NODE NODE PIX PIX PIX PIX PIX PIX PIX X PIX NIC9 NODE NODE PIX PIX PIX PIX PIX PIX PIX PIX X Legend: X = Self SYS = Connection traversing PCIe as well as the SMP interconnect between NUMA nodes (e.g., QPI/UPI) NODE = Connection traversing PCIe as well as the interconnect between PCIe Host Bridges within a NUMA node PHB = Connection traversing PCIe as well as a PCIe Host Bridge (typically the CPU) PXB = Connection traversing multiple PCIe bridges (without traversing the PCIe Host Bridge) PIX = Connection traversing at most a single PCIe bridge NIC Legend: NIC0: mlx5_0 NIC1: mlx5_1 NIC2: mlx5_2 NIC3: mlx5_3 NIC4: mlx5_4 NIC5: mlx5_5 NIC6: mlx5_6 NIC7: mlx5_7 NIC8: mlx5_8 NIC9: mlx5_9

Now let's run a few gdsio tests across our RDMA nfs mount. Please note these runs were not performance tuned in any way.  This is merely a demonstration to show the feature functionality.   

In this first example, gdsio is used to generate a random write load of small IOs (4k) to one of the NFS mount point

sh-5.1# /usr/local/cuda-12.8/gds/tools/gdsio -D /nfsfast -d 0 -w 32 -s 500M -i 4K -x 0 -I 3 -T 120 IoType: RANDWRITE XferType: GPUD Threads: 32 DataSetSize: 43222136/16384000(KiB) IOSize: 4(KiB) Throughput: 0.344940 GiB/sec, Avg_Latency: 352.314946 usecs ops: 10805534 total_time 119.498576 secs

Next we will repeat the same test but for random reads.

sh-5.1# /usr/local/cuda-12.8/gds/tools/gdsio -D /nfsfast -d 0 -w 32 -s 500M -i 4K -x 0 -I 2 -T 120 IoType: RANDREAD XferType: GPUD Threads: 32 DataSetSize: 71313540/16384000(KiB) IOSize: 4(KiB) Throughput: 0.569229 GiB/sec, Avg_Latency: 214.448246 usecs ops: 17828385 total_time 119.477201 secs

Small and random IOs are all about IOPS and latency. For our next test we will determine throughput. We will use larger files sizes and much larger IO sizes.

sh-5.1# /usr/local/cuda-12.8/gds/tools/gdsio -D /nfsfast -d 0 -w 32 -s 1G -i 1M -x 0 -I 1 -T 120 IoType: WRITE XferType: GPUD Threads: 32 DataSetSize: 320301056/33554432(KiB) IOSize: 1024(KiB) Throughput: 2.547637 GiB/sec, Avg_Latency: 12487.658159 usecs ops: 312794 total_time 119.900455 secs

This concludes the workflow of configuring and testing GPU Direct Storage on OpenShift over an RDMA NFS mount.

Monday, April 13, 2020

Open Cluster Management Inside CodeReady Containers



Wouldn't it be great to control a set of OpenShift or Kubernetes clusters deployed on a variety of cloud and/or baremetal platforms from a centralized hub?  This is exactly what Open Cluster Management aims to achieve but getting it up and running in production requires an already deployed OpenShift or Kubernetes cluster.  In the following blog I will describe how one could configure CodeReady Containers and deploy Open Cluster Management in the environment for non production feature functionality testing purposes.

CodeReady Containers brings a minimal, preconfigured OpenShift 4.1 or newer cluster to your local laptop or desktop computer for development and testing purposes. CodeReady Containers is delivered as a Red Hat Enterprise Linux virtual machine that supports native hypervisors for Linux, macOS, and Windows 10.  In the example below we will be using a Red Hat Enterprise Linux 8 host with virtualization services enabled to launch our CodeReady Container environment that will eventually run Open Cluster Management.

The first step is to obtain the CodeReady Container distribution from Red Hat at the following: https://developers.redhat.com/products/codeready-containers/overview  Click the download button and it will redirect to a page where the the platform download of choice can be chosen.

Once we have downloaded the crc-linux-amd64.tar.xz we can extract it, go into the extracted directory and run crc setup:

$ tar -xf crc-linux-amd64.tar.xz
$ cd crc-linux-1.8.0-amd64/
$ crc setup
INFO Checking if oc binary is cached              
INFO Checking if podman remote binary is cached   
INFO Checking if CRC bundle is cached in '$HOME/.crc' 
INFO Checking if running as non-root              
INFO Checking if Virtualization is enabled        
INFO Checking if KVM is enabled                   
INFO Checking if libvirt is installed             
INFO Checking if user is part of libvirt group    
INFO Checking if libvirt is enabled               
INFO Checking if libvirt daemon is running        
INFO Checking if a supported libvirt version is installed 
INFO Checking if crc-driver-libvirt is installed  
INFO Checking for obsolete crc-driver-libvirt     
INFO Checking if libvirt 'crc' network is available 
INFO Checking if libvirt 'crc' network is active  
INFO Checking if NetworkManager is installed      
INFO Checking if NetworkManager service is running 
INFO Checking if /etc/NetworkManager/conf.d/crc-nm-dnsmasq.conf exists 
INFO Checking if /etc/NetworkManager/dnsmasq.d/crc.conf exists 
Setup is complete, you can now run 'crc start' to start the OpenShift cluster

Now that setup is complete I want to modify a few of the configurations before we start the virtual machine.  Below we will modify the number of cpus and memory allocated to the virtual machine as the default is not enough to run Open Cluster Management in a CodeReady container:

$ crc config set cpus 6
Changes to configuration property 'cpus' are only applied when a new CRC instance is created.
If you already have a CRC instance, then for this configuration change to take effect, delete the CRC instance with 'crc delete' and start a new one with 'crc start'.
$ crc config set memory 24000
Changes to configuration property 'memory' are only applied when a new CRC instance is created.
If you already have a CRC instance, then for this configuration change to take effect, delete the CRC instance with 'crc delete' and start a new one with 'crc start'.

Now that we have increased our cpu and memory resources we can start the virtual machine that will run the CodeReady container which will ultimately run a single node OpenShift environment that Open Cluster Management can run on.  During the startup the image pull secret will be required which is obtained on the same download page where the CodeReady Container was retrieved.

$ crc start
INFO Checking if oc binary is cached              
INFO Checking if podman remote binary is cached   
INFO Checking if running as non-root              
INFO Checking if Virtualization is enabled        
INFO Checking if KVM is enabled                   
INFO Checking if libvirt is installed             
INFO Checking if user is part of libvirt group    
INFO Checking if libvirt is enabled               
INFO Checking if libvirt daemon is running        
INFO Checking if a supported libvirt version is installed 
INFO Checking if crc-driver-libvirt is installed  
INFO Checking if libvirt 'crc' network is available 
INFO Checking if libvirt 'crc' network is active  
INFO Checking if NetworkManager is installed      
INFO Checking if NetworkManager service is running 
INFO Checking if /etc/NetworkManager/conf.d/crc-nm-dnsmasq.conf exists 
INFO Checking if /etc/NetworkManager/dnsmasq.d/crc.conf exists 
? Image pull secret [? for help] **************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************

INFO Loading bundle: crc_libvirt_4.3.8.crcbundle ... 
INFO Checking size of the disk image /home/bschmaus/.crc/cache/crc_libvirt_4.3.8/crc.qcow2 ... 
INFO Creating CodeReady Containers VM for OpenShift 4.3.8... 
INFO Verifying validity of the cluster certificates ... 
INFO Check internal and public DNS query ...      
INFO Check DNS query from host ...                
INFO Copying kubeconfig file to instance dir ...  
INFO Adding user's pull secret ...                
INFO Updating cluster ID ...                      
INFO Starting OpenShift cluster ... [waiting 3m]  
INFO                                              
INFO To access the cluster, first set up your environment by following 'crc oc-env' instructions 
INFO Then you can access it by running 'oc login -u developer -p developer https://api.crc.testing:6443' 
INFO To login as an admin, run 'oc login -u kubeadmin -p kKdPx-pjmWe-b3kuu-jeZm3 https://api.crc.testing:6443' 
INFO                                              
INFO You can now run 'crc console' and use these credentials to access the OpenShift web console 
Started the OpenShift cluster
WARN The cluster might report a degraded or error state. This is expected since several operators have been disabled to lower the resource usage. For more information, please consult the documentation 

Before we continue lets confirm we can access our CodeReady Container OpenShift cluster:

$ crc oc-env
export PATH="/home/bschmaus/.crc/bin:$PATH"
# Run this command to configure your shell:
# eval $(crc oc-env)
$ eval $(crc oc-env)
$ oc get co
NAME                                       VERSION   AVAILABLE   PROGRESSING   DEGRADED   SINCE
authentication                             4.3.8     True        False         False      17d
cloud-credential                           4.3.8     True        False         False      17d
cluster-autoscaler                         4.3.8     True        False         False      17d
console                                    4.3.8     True        False         False      17d
dns                                        4.3.8     True        False         False      8m46s
image-registry                             4.3.8     True        False         False      17d
ingress                                    4.3.8     True        False         False      17d
insights                                   4.3.8     True        False         False      17d
kube-apiserver                             4.3.8     True        False         False      17d
kube-controller-manager                    4.3.8     True        False         False      17d
kube-scheduler                             4.3.8     True        False         False      17d
machine-api                                4.3.8     True        False         False      17d
machine-config                             4.3.8     True        False         False      17d
marketplace                                4.3.8     True        False         False      8m17s
monitoring                                 4.3.8     True        False         False      17d
network                                    4.3.8     True        False         False      17d
node-tuning                                4.3.8     True        False         False      8m42s
openshift-apiserver                        4.3.8     True        False         False      17d
openshift-controller-manager               4.3.8     True        False         False      17d
openshift-samples                          4.3.8     True        False         False      17d
operator-lifecycle-manager                 4.3.8     True        False         False      17d
operator-lifecycle-manager-catalog         4.3.8     True        False         False      17d
operator-lifecycle-manager-packageserver   4.3.8     True        False         False      8m21s
service-ca                                 4.3.8     True        False         False      17d
service-catalog-apiserver                  4.3.8     True        False         False      17d
service-catalog-controller-manager         4.3.8     True        False         False      17d
storage                                    4.3.8     True        False         False      17d


So far everything looks good so lets continue.

Another requirement for Open Cluster Management is a default storage class.  In a previous blog I discussed how to enable a Netapp Simulator in KVM.  Now I can finally use it with along with the Trident CSI driver which can be obtained here: https://github.com/NetApp/trident/releases  I am going with the current version as of this writing which is v20.01.1.  Begin by downloading the release to a host that has access to the CodeReady Container we just started.  Then extract the release:

$ tar -xzf trident-installer-20.01.1.tar.gz
$ cd trident-installer/
$ ls
backend.json  extras  pvc.yaml  sample-input  sc.yaml  tridentctl

In my directory listing I have a few files I created: backend.json which instructs the Trident driver how to talk to the Netapp appliance and the sc.yaml which I can use to define a storageclass on my CodeReady Container OpenShift environment.  Lets take a quick look at the backend.json:

{
"debug":true,
"managementLIF":"192.168.0.21",
"dataLIF":"192.168.0.22",
"svm":"test",
"backendName": "nas_backend",
"aggregate":"aggr0",
"username":"admin",
"password":"password",
"storageDriverName":"ontap-nas",
"storagePrefix":"schmaustech_",
"version":1
}

Now lets look at the sc.yaml file:

apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: nas
provisioner: csi.trident.netapp.io
parameters:
backendType: "ontap-nas"
snapshots: "True"
provisioningType: "thin"
encryption: "true"

At this point lets login as kubeadmin and install the Trident CSI driver and then show what pods got created:

$ oc login -u kubeadmin -p kKdPx-pjmWe-b3kuu-jeZm3 https://api.crc.testing:6443
Login successful.

You have access to 53 projects, the list has been suppressed. You can list all projects with 'oc projects'

Using project "default".

$ tridentctl install -n trident-ns
INFO Starting Trident installation.                namespace=trident-ns
INFO Created namespace.                            namespace=trident-ns
INFO Created service account.                     
INFO Created cluster role.                        
INFO Created cluster role binding.                
INFO Added security context constraint user.       scc=privileged user=trident-csi
INFO Created custom resource definitions.          namespace=trident-ns
INFO Created Trident pod security policy.         
INFO Added finalizers to custom resource definitions. 
INFO Created Trident service.                     
INFO Created Trident secret.                      
INFO Created Trident deployment.                  
INFO Created Trident daemonset.                   
INFO Waiting for Trident pod to start.            
INFO Trident pod started.                          namespace=trident-ns pod=trident-csi-d8667b7fd-sgxz2
INFO Waiting for Trident REST interface.          
INFO Trident REST interface is up.                 version=20.01.1
INFO Trident installation succeeded.
              
$ oc get pods -n trident-ns -o wide
NAME                          READY   STATUS    RESTARTS   AGE     IP               NODE                 NOMINATED NODE   READINESS GATES
trident-csi-2n5lm             2/2     Running   0          2m32s   192.168.130.11   crc-45nsk-master-0              
trident-csi-d8667b7fd-sgxz2   4/4     Running   0          2m33s   10.128.0.101     crc-45nsk-master-0              


With Trident installed we can now use the backend.json we created to configure the driver to talk to the Netapp:

$ tridentctl create backend -f backend.json -n trident-ns
+-------------+----------------+--------------------------------------+--------+---------+
|    NAME     | STORAGE DRIVER |                 UUID                 | STATE  | VOLUMES |
+-------------+----------------+--------------------------------------+--------+---------+
| nas_backend | ontap-nas      | 6567da6d-23ca-4d09-9730-9a931fe21275 | online |       0 |
+-------------+----------------+--------------------------------------+--------+---------+


With the nas backend defined and online we can go ahead and create the storageclass.  Note that we will also have to set this storage class to the default as Open Cluster Management will be looking for a default storageclass.

$ oc create -f sc.yaml
storageclass.storage.k8s.io/nas created

$ oc get storageclass
NAME   PROVISIONER             AGE
nas    csi.trident.netapp.io   6s

$ oc patch storageclass nas -p '{"metadata": {"annotations":{"storageclass.kubernetes.io/is-default-class":"true"}}}'
storageclass.storage.k8s.io/nas patched

$ oc get storageclass
NAME            PROVISIONER             AGE
nas (default)   csi.trident.netapp.io   30s


At this point we have all the requirements necessary to install Open Cluster Management thus we can pivot to the process of installing Open Cluster Management.  First lets clone the repository:

$ git clone https://github.com/open-cluster-management/deploy.git
Cloning into 'deploy'...
remote: Enumerating objects: 136, done.
remote: Counting objects: 100% (136/136), done.
remote: Compressing objects: 100% (78/78), done.
remote: Total 702 (delta 88), reused 86 (delta 55), pack-reused 566
Receiving objects: 100% (702/702), 466.26 KiB | 3.67 MiB/s, done.
Resolving deltas: 100% (371/371), done.

Next we will need to  create a pull-secret.yaml that looks similar to the following however each user will need to obtain their own pull-secret.  Directions on how to obtain pull-secret can be found here: https://github.com/open-cluster-management/deploy  The pull-secret.yaml should be created under deploy/prereqs

apiVersion: v1
kind: Secret
metadata:
  name: multiclusterhub-operator-pull-secret
data:
  .dockerconfigjson: PULL-SECRET-ENCRYPTED-PASSWORD-HERE
type: kubernetes.io/dockerconfigjson

vi deploy/prereqs/pull-secret.yaml

Next export the kubeconfig for the CodeReady Container Openshift environment:

$ export KUBECONFIG=/home/bschmaus/.crc/cache/crc_libvirt_4.3.8/kubeconfig

Now lets run the start.sh script inside of the deploy directory from the Open Cluster Management repository that was cloned. Select the default snapshot or enter a known good version when prompted:

$ ./start.sh --watch
* Testing connection
* Using baseDomain: apps-crc.testing
* oc CLI Client Version: 4.3.10-202003280552-6a90d0a
OK: Default Storage Class defined
Find snapshot tags @ https://quay.io/repository/open-cluster-management/multiclusterhub-operator-index?tab=tags
Enter SNAPSHOT TAG: (Press ENTER for default: 1.0.0-SNAPSHOT-2020-03-31-02-16-43)

After accepting the default snapshot or applying a specified one the installer will move along and apply some prerequisites and then pull down the requirements for the multicluster-hub-operator and then bring them to a running state:

* Using: 1.0.0-SNAPSHOT-2020-03-31-02-16-43

* Applying SNAPSHOT to multiclusterhub-operator subscription
* Applying multicluster-hub-cr values

##### Applying prerequisites
namespace/hive created
namespace/open-cluster-management created
secret/multiclusterhub-operator-pull-secret created
Error from server (AlreadyExists): error when creating "prereqs/": serviceaccounts "default" already exists

##### Applying multicluster-hub-operator subscription #####
service/open-cluster-management-registry created
deployment.apps/open-cluster-management-registry created
operatorgroup.operators.coreos.com/default created
catalogsource.operators.coreos.com/open-cluster-management created
subscription.operators.coreos.com/multiclusterhub-operator-bundle created

#####
Wait for multiclusterhub-operator to reach running state (4min).
* STATUS: Waiting
* STATUS: Waiting
* STATUS: Waiting
* STATUS: Waiting
* STATUS: Waiting
* STATUS: Waiting
* STATUS: Waiting
* STATUS: Waiting
* STATUS: Waiting
* STATUS: Waiting
* STATUS: Waiting
* STATUS: Waiting
* STATUS: Waiting
* STATUS: Waiting
* STATUS: Waiting
* STATUS: Waiting
* STATUS: multiclusterhub-operator-54d98758f5-xdhkn           0/1     ContainerCreating   0          1s
* STATUS: multiclusterhub-operator-54d98758f5-xdhkn                         0/1     ContainerCreating   0          4s
* STATUS: multiclusterhub-operator-54d98758f5-xdhkn                         0/1     ContainerCreating   0          7s
* STATUS: multiclusterhub-operator-54d98758f5-xdhkn                         0/1     ContainerCreating   0          11s
* STATUS: multiclusterhub-operator-54d98758f5-xdhkn                         0/1     ContainerCreating   0          14s
* STATUS: multiclusterhub-operator-54d98758f5-xdhkn                         0/1     ContainerCreating   0          17s
* STATUS: multiclusterhub-operator-54d98758f5-xdhkn                         0/1     ContainerCreating   0          20s
* STATUS: multiclusterhub-operator-54d98758f5-xdhkn                         0/1     ContainerCreating   0          23s
* STATUS: multiclusterhub-operator-54d98758f5-xdhkn                         0/1     ContainerCreating   0          26s
* STATUS: multiclusterhub-operator-54d98758f5-xdhkn                         0/1     ContainerCreating   0          29s
* multiclusterhub-operator is running

* Beginning deploy...
* Applying the multiclusterhub-operator to install Red Hat Advanced Cluster Management for Kubernetes multiclusterhub.operators.open-cluster-management.io/multiclusterhub created

#####
Wait for multicluster-operators-application to reach running state (4min).
* STATUS: multicluster-operators-application-5d68b77964-swfgp               0/4     ContainerCreating   0          31s
* STATUS: multicluster-operators-application-5d68b77964-swfgp               0/4     ContainerCreating   0          34s
* STATUS: multicluster-operators-application-5d68b77964-swfgp               0/4     ContainerCreating   0          37s
* STATUS: multicluster-operators-application-5d68b77964-swfgp               0/4     ContainerCreating   0          41s
* STATUS: multicluster-operators-application-5d68b77964-swfgp               0/4     ContainerCreating   0          44s
* STATUS: multicluster-operators-application-5d68b77964-swfgp               0/4     ContainerCreating   0          47s
* STATUS: multicluster-operators-application-5d68b77964-swfgp               0/4     ContainerCreating   0          50s
* STATUS: multicluster-operators-application-5d68b77964-swfgp               0/4     Running             0          53s
* STATUS: multicluster-operators-application-5d68b77964-swfgp               2/4     Running   0          56s
* STATUS: multicluster-operators-application-5d68b77964-swfgp               2/4     Running   0          59s
* STATUS: multicluster-operators-application-5d68b77964-swfgp               2/4     Running   0          62s
* STATUS: multicluster-operators-application-5d68b77964-swfgp               2/4     Running   0          65s
* STATUS: multicluster-operators-application-5d68b77964-swfgp               3/4     Running   0          68s
* multicluster-operators-application is running

Once the multiclusterhub-operator is up and running it will proceed to deploy the Open Cluster Management pods:

NAME                                                              READY   STATUS    RESTARTS   AGE
etcd-operator-558567f79d-g65zj                                    3/3     Running   0          79s
multicluster-operators-application-5d68b77964-swfgp               4/4     Running   0          82s
multicluster-operators-hub-subscription-85445d9d7-9qb28           1/1     Running   0          82s
multicluster-operators-standalone-subscription-845764c484-nqgps   1/1     Running   0          82s
multiclusterhub-operator-54d98758f5-xdhkn                         1/1     Running   0          84s
multiclusterhub-repo-54b6fd847c-s5md7                             1/1     Running   0          42s
open-cluster-management-registry-74657d9c7b-k5vfk                 1/1     Running   0          2m25s

Number of expected Pods : 7/35
Pods still NOT running  : 0
Detected ACM Console URL: https://


The display will turn into a display similar to a watch and will show the progress of the pods being deployed:

NAME                                                              READY   STATUS              RESTARTS   AGE
application-chart-60648-applicationui-84f666fb-zhjq5              0/1     ContainerCreating   0          30s
cert-manager-8fae3-6cd9985bd6-jtwrk                               1/1     Running             0          64s
cert-manager-webhook-0d3cc-cainjector-5c9846b48b-pt774            1/1     Running             0          40s
cert-manager-webhook-85dbd49676-hc9qr                             1/1     Running             0          40s
configmap-watcher-42800-b86cbf8cb-xpw7g                           1/1     Running             0          41s
etcd-cluster-4z7s9dhx9j                                           0/1     PodInitializing     0          49s
etcd-operator-558567f79d-g65zj                                    3/3     Running             0          2m32s
grc-535c7-grcui-698dc78d6f-62bbm                                  0/1     ContainerCreating   0          31s
grc-535c7-grcuiapi-847f5df869-q62tb                               0/1     ContainerCreating   0          31s
grc-535c7-policy-postinstall-kglbr                                0/1     ContainerCreating   0          30s
grc-535c7-policy-propogator-6f8684c78-7mm8b                       0/1     ContainerCreating   0          31s
mcm-apiserver-6799bddcf5-645nd                                    0/1     ContainerCreating   0          38s
mcm-apiserver-7bc995d77-pl4qr                                     0/1     ContainerCreating   0          49s
mcm-controller-8555975b78-m9nst                                   0/1     ContainerCreating   0          49s
mcm-webhook-8475bb4fd6-8vhsb                                      0/1     ContainerCreating   0          48s
multicluster-operators-application-5d68b77964-swfgp               4/4     Running             0          2m35s
multicluster-operators-hub-subscription-85445d9d7-9qb28           1/1     Running             0          2m35s
multicluster-operators-standalone-subscription-845764c484-nqgps   1/1     Running             0          2m35s
multiclusterhub-operator-54d98758f5-xdhkn                         1/1     Running             0          2m37s
multiclusterhub-repo-54b6fd847c-s5md7                             1/1     Running             0          115s
open-cluster-management-registry-74657d9c7b-k5vfk                 1/1     Running             0          3m38s
rcm-controller-5cf46f6f6b-8c5nc                                   0/1     ContainerCreating   0          31s

Number of expected Pods : 22/35
Pods still NOT running  : 11
Detected ACM Console URL: https://


Once the installation is complete the following should be a summary of what was deployed:

NAME                                                              READY   STATUS    RESTARTS   AGE
application-chart-60648-applicationui-84f666fb-zhjq5              1/1     Running   0          5m27s
cert-manager-8fae3-6cd9985bd6-jtwrk                               1/1     Running   0          6m1s
cert-manager-webhook-0d3cc-cainjector-5c9846b48b-pt774            1/1     Running   0          5m37s
cert-manager-webhook-85dbd49676-hc9qr                             1/1     Running   0          5m37s
configmap-watcher-42800-b86cbf8cb-xpw7g                           1/1     Running   0          5m38s
console-chart-eef51-consoleapi-64ff64d5b6-sjncl                   1/1     Running   0          3m21s
console-chart-eef51-consoleui-57b5955d98-kfmd2                    1/1     Running   0          3m21s
console-header-85d8f49c7b-twt9v                                   1/1     Running   0          3m21s
etcd-cluster-4z7s9dhx9j                                           1/1     Running   0          5m46s
etcd-cluster-bw25r8ph5p                                           1/1     Running   0          4m41s
etcd-cluster-mtk6fpm9bm                                           1/1     Running   0          4m9s
etcd-operator-558567f79d-g65zj                                    3/3     Running   0          7m29s
grc-535c7-grcui-698dc78d6f-8h57n                                  1/1     Running   0          2m2s
grc-535c7-grcuiapi-847f5df869-fjk55                               1/1     Running   0          2m2s
grc-535c7-policy-propogator-6f8684c78-x724n                       1/1     Running   0          2m2s
kui-web-terminal-66f6c5b89-mfd8p                                  1/1     Running   0          3m18s
management-ingress-80cda-55dcd89b87-nbkpc                         2/2     Running   0          3m21s
mcm-apiserver-7bc995d77-pl4qr                                     1/1     Running   0          5m46s
mcm-controller-8555975b78-m9nst                                   1/1     Running   0          5m46s
mcm-webhook-8475bb4fd6-8vhsb                                      1/1     Running   0          5m45s
multicluster-mongodb-0                                            1/1     Running   0          3m15s
multicluster-operators-application-5d68b77964-swfgp               4/4     Running   3          7m32s
multicluster-operators-hub-subscription-85445d9d7-9qb28           1/1     Running   0          7m32s
multicluster-operators-standalone-subscription-845764c484-nqgps   1/1     Running   0          7m32s
multiclusterhub-operator-54d98758f5-xdhkn                         1/1     Running   0          7m34s
multiclusterhub-repo-54b6fd847c-s5md7                             1/1     Running   0          6m52s
open-cluster-management-registry-74657d9c7b-k5vfk                 1/1     Running   0          8m35s
rcm-controller-5cf46f6f6b-8c5nc                                   1/1     Running   3          5m28s
search-operator-544f7c6cf6-vwrrv                                  1/1     Running   0          3m11s
search-prod-d314c-redisgraph-5c8fc4d6dc-2xx6g                     1/1     Running   0          3m11s
search-prod-d314c-search-aggregator-7c9fd68949-jk7cz              1/1     Running   0          3m11s
search-prod-d314c-search-api-7df556b7d7-xkn2k                     1/1     Running   0          3m11s
search-prod-d314c-search-collector-9b44f9f5c-pzrkb                1/1     Running   0          3m11s
topology-592b5-topology-5d5c75c484-f2str                          1/1     Running   0          3m23s
topology-592b5-topologyapi-6fdcc8dc4c-c9v49                       1/1     Running   0          3m23s

Number of expected Pods : 35/35
Pods still NOT running  : 0
Detected ACM Console URL: https://multicloud-console.apps-crc.testing


We can further validate the deployment by looking at the following two namespaces: open-cluster-management & hive

$ oc get pods -n open-cluster-management
NAME                                                              READY   STATUS    RESTARTS   AGE
application-chart-60648-applicationui-84f666fb-zhjq5              1/1     Running   0          18m
cert-manager-8fae3-6cd9985bd6-jtwrk                               1/1     Running   0          18m
cert-manager-webhook-0d3cc-cainjector-5c9846b48b-pt774            1/1     Running   0          18m
cert-manager-webhook-85dbd49676-hc9qr                             1/1     Running   0          18m
configmap-watcher-42800-b86cbf8cb-xpw7g                           1/1     Running   0          18m
console-chart-eef51-consoleapi-64ff64d5b6-sjncl                   1/1     Running   0          16m
console-chart-eef51-consoleui-57b5955d98-kfmd2                    1/1     Running   0          16m
console-header-85d8f49c7b-twt9v                                   1/1     Running   0          16m
etcd-cluster-4z7s9dhx9j                                           1/1     Running   0          18m
etcd-cluster-bw25r8ph5p                                           1/1     Running   0          17m
etcd-cluster-mtk6fpm9bm                                           1/1     Running   0          17m
etcd-operator-558567f79d-g65zj                                    3/3     Running   0          20m
grc-535c7-grcui-698dc78d6f-8h57n                                  1/1     Running   0          14m
grc-535c7-grcuiapi-847f5df869-fjk55                               1/1     Running   0          14m
grc-535c7-policy-propogator-6f8684c78-x724n                       1/1     Running   0          14m
kui-web-terminal-66f6c5b89-mfd8p                                  1/1     Running   0          16m
management-ingress-80cda-55dcd89b87-nbkpc                         2/2     Running   0          16m
mcm-apiserver-7bc995d77-pl4qr                                     1/1     Running   0          18m
mcm-controller-8555975b78-m9nst                                   1/1     Running   0          18m
mcm-webhook-8475bb4fd6-8vhsb                                      1/1     Running   0          18m
multicluster-mongodb-0                                            1/1     Running   0          16m
multicluster-operators-application-5d68b77964-swfgp               4/4     Running   3          20m
multicluster-operators-hub-subscription-85445d9d7-9qb28           1/1     Running   0          20m
multicluster-operators-standalone-subscription-845764c484-nqgps   1/1     Running   0          20m
multiclusterhub-operator-54d98758f5-xdhkn                         1/1     Running   0          20m
multiclusterhub-repo-54b6fd847c-s5md7                             1/1     Running   0          19m
open-cluster-management-registry-74657d9c7b-k5vfk                 1/1     Running   0          21m
rcm-controller-5cf46f6f6b-8c5nc                                   1/1     Running   3          18m
search-operator-544f7c6cf6-vwrrv                                  1/1     Running   0          16m
search-prod-d314c-redisgraph-5c8fc4d6dc-2xx6g                     1/1     Running   0          16m
search-prod-d314c-search-aggregator-7c9fd68949-jk7cz              1/1     Running   0          16m
search-prod-d314c-search-api-7df556b7d7-xkn2k                     1/1     Running   0          16m
search-prod-d314c-search-collector-9b44f9f5c-pzrkb                1/1     Running   0          16m
topology-592b5-topology-5d5c75c484-f2str                          1/1     Running   0          16m
topology-592b5-topologyapi-6fdcc8dc4c-c9v49                       1/1     Running   0          16m
[bschmaus@cube ~]$ oc get pods -n hive
NAME                                READY   STATUS    RESTARTS   AGE
hive-controllers-74894574b5-m89xw   1/1     Running   0          15m
hive-operator-7cd7488667-jcb2m      1/1     Running   1          18m
hiveadmission-7965ffd69-dx9zg       1/1     Running   0          15m
hiveadmission-7965ffd69-mmn7t       1/1     Running   0          15m

Everything looks good from command line so lets validate one final way to confirm the installation is complete by looking at the web UI.  Since the hypervisor host that the environment is running on does not have direct network connectivity I am going to leverage VNC server on the hypervisor host to access the web UI.  I am not going to go into those details because there are plenty of documented ways to use VNC on the web.  However once a web browser is available via vncserver the kubeadmin username and password will be required to login.  Those credentials can be found in the CodeReady Container kubeadmin-password file:

$ /home/bschmaus/.crc/cache/crc_libvirt_4.3.8/kubeadmin-password

Below is an example of the login screen displayed when accessing the URL from Open Cluster Management installation log:


And once logged in we find the welcome screen:



At this point we are ready to configure and deploy a OpenShift cluster using the Open Cluster Management however I will save that for a blog another day!