Prerequisites
-
OpenShift cluster: OpenShift 4.x already deployed
-
Nodes: Worker nodes healthy and schedulable
-
FlashArray: Configured and reachable for management + data path
-
Connectivity: IP reachability between worker nodes and FlashArray
-
Access: Admin/cluster‑admin rights on OpenShift and FlashArray
Pre‑Flight Validation Commands
Run these checks before applying any MachineConfigs or deploying Portworx. These commands confirm prerequisites — they do not configure anything.
# The below command check overall cluster operators & all operators should show Available=True, Degraded=False
oc get co
# The below command check overall and their status & all participating worker nodes should show Ready
oc get nodes -o wide
Management Connectivity to FlashArray (from a worker or bastion)
# Enter a node through debug mode to check connectivity
oc get nodes
# Start a debug session on a worker node
oc debug node/<worker-node-name>
# enter host binaries
chroot /host
# Basic reachability (if ICMP is permitted)
ping <flasharray-mgmt-ip>
# TCP reachability on management port 443
socat connects and hangs on success; errors indicate unreachable
socat - TCP:<flasharray-mgmt-ip>:443
oc get nodes
# Start a debug session on a worker node
oc debug node/<worker-node-name>
# enter host binaries
chroot /host
# Confirm iSCSI initiator name exists and is unique per node
cat /etc/iscsi/initiatorname.iscsi
# Confirm iSCSI service is available
systemctl status iscsid
# Confirm multipathd is running
systemctl status multipathd
# Optional: TCP connectivity to FlashArray iSCSI port
# Hangs on success; error indicates routing or firewall issue
socat - TCP:<flasharray-iscsi-ip>:3260
-
Connect OpenShift Workers to FlashArray (Per Transport):
Each applicable worker node must have:
-
Unique iSCSI Initiator IQN: Run the following command on each worker node:
cat /etc/iscsi/initiatorname.iscsiThe output must contain a non-empty and unique InitiatorName value for that node. For example:
InitiatorName=iqn.1994-05.com.redhat:2357875188ed -
Device Mapper Multipath running: Multipath support must be enabled on each worker node. Verify the service is
activesystemctlstatusmultipathd.Note:DM-Multipath consolidates multiple iSCSI paths to the same LUN into a single logical device. If multipathd is not yet configured, run the following to enable it before proceeding:
mpathconf --enablemodprobe dm_multipathsystemctl start multipathdThese are one-time setup steps. Once configured, multipathd should start automatically on subsequent boots.
-
Optional Verification: Basic connectivity verification can be performed from any worker node once the FlashArray has been configured with host objects and volumes are connected to the host group.
-
Discover iSCSI targets
iscsiadm -m discovery -t sendtargets -p <flasharray-iscsi-ip>Example output:
10.x.x.x:3260,2137 iqn.2010-06.com.purestorage:flasharray.64724aac22257212 10.x.x.x:3260,2137 iqn.2010-06.com.purestorage:flasharray.64724aac22257212 -
Log in to discovered targets:
iscsiadm -m node --loginall=automaticThis logs the initiator in to all discovered targets. After login, SCSI block devices will appear on the worker node corresponding to each mapped volume path.
-
Verify multipath devices:
multipath -llExample output:
mpatha (3624a93708eabcb40cc4241b209e0d22b) dm-0 PURE,FlashArray size=500G features='0' hwhandler='1 alua' wp=rw `-+- policy='service-time 0' prio=50 status=active |- 33:0:0:254 sdb 8:16 active ready running |- 34:0:0:254 sdc 8:32 active ready running
-
-
Unique iSCSI Initiator IQN: Run the following command on each worker node:
-
Configure Transport Protocol (iSCSI MachineConfig):
This ensures the iscsid service is enabled and multipathing is configured for iSCSI.
apiVersion: machineconfiguration.openshift.io/v1 kind: MachineConfig metadata: name: 99-worker-pure-storage labels: machineconfiguration.openshift.io/role: worker spec: config: ignition: version: 3.2.0 storage: files: # Create /etc/multipath.conf - path: /etc/multipath.conf mode: 420 overwrite: true contents: source: data:,defaults%20%7B%0A%20%20%20%20user_friendly_names%20no%0A%20%20%20%20enable_foreign%20%22%5E%24%22%0A%20%20%20%20polling_interval%2010%0A%20%20%20%20find_multipaths%20yes%0A%7D%0Adevices%20%7B%0A%20%20%20%20device%20%7B%0A%20%20%20%20%20%20%20%20vendor%20%22NVME%22%0A%20%20%20%20%20%20%20%20product%20%22Everpure%20FlashArray%22%0A%20%20%20%20%20%20%20%20path_selector%20%22queue-length%200%22%0A%20%20%20%20%20%20%20%20path_grouping_policy%20group_by_prio%0A%20%20%20%20%20%20%20%20prio%20ana%0A%20%20%20%20%20%20%20%20failback%20immediate%0A%20%20%20%20%20%20%20%20fast_io_fail_tmo%2010%0A%20%20%20%20%20%20%20%20user_friendly_names%20no%0A%20%20%20%20%20%20%20%20no_path_retry%200%0A%20%20%20%20%20%20%20%20features%200%0A%20%20%20%20%20%20%20%20dev_loss_tmo%2060%0A%20%20%20%20%7D%0A%20%20%20%20device%20%7B%0A%20%20%20%20%20%20%20%20vendor%20%22PURE%22%0A%20%20%20%20%20%20%20%20product%20%22FlashArray%22%0A%20%20%20%20%20%20%20%20path_selector%20%22service-time%200%22%0A%20%20%20%20%20%20%20%20hardware_handler%20%221%20alua%22%0A%20%20%20%20%20%20%20%20path_grouping_policy%20group_by_prio%0A%20%20%20%20%20%20%20%20prio%20alua%0A%20%20%20%20%20%20%20%20failback%20immediate%0A%20%20%20%20%20%20%20%20path_checker%20tur%0A%20%20%20%20%20%20%20%20fast_io_fail_tmo%2010%0A%20%20%20%20%20%20%20%20user_friendly_names%20no%0A%20%20%20%20%20%20%20%20no_path_retry%200%0A%20%20%20%20%20%20%20%20features%200%0A%20%20%20%20%20%20%20%20dev_loss_tmo%20600%0A%20%20%20%20%7D%0A%7D%0Ablacklist_exceptions%20%7B%0A%20%20%20%20property%20%22(SCSI_IDENT_%7CID_WWN)%22%0A%7D%0Ablacklist%20%7B%0A%20%20%20%20devnode%20%22%5Epxd%5B0-9%5D*%22%0A%20%20%20%20devnode%20%22%5Epxd*%22%0A%20%20%20%20device%20%7B%0A%20%20%20%20%20%20%20%20vendor%20%22VMware%22%0A%20%20%20%20%20%20%20%20product%20%22Virtual%20disk%22%0A%20%20%20%20%7D%0A%7D # Create /etc/udev/rules.d/99-pure-storage.rules - path: /etc/udev/rules.d/99-pure-storage.rules mode: 420 overwrite: true contents: source: data:,ACTION%3D%3D%22add%7Cchange%22%2C%20KERNEL%3D%3D%22sd*%5B!0-9%5D%22%2C%20SUBSYSTEM%3D%3D%22block%22%2C%20ENV%7BID_VENDOR%7D%3D%3D%22PURE%22%2C%20OPTIONS%3D%22nowatch%22%2C%20ATTR%7Bqueue%2Fscheduler%7D%3D%22none%22%0AACTION%3D%3D%22add%7Cchange%22%2C%20KERNEL%3D%3D%22dm-%5B0-9%5D*%22%2C%20SUBSYSTEM%3D%3D%22block%22%2C%20ENV%7BDM_NAME%7D%3D%3D%223624a937*%22%2C%20OPTIONS%3D%22nowatch%22%2C%20ATTR%7Bqueue%2Fscheduler%7D%3D%22none%22%0AACTION%3D%3D%22add%7Cchange%22%2C%20KERNEL%3D%3D%22sd*%5B!0-9%5D%22%2C%20SUBSYSTEM%3D%3D%22block%22%2C%20ENV%7BID_VENDOR%7D%3D%3D%22PURE%22%2C%20OPTIONS%3D%22nowatch%22%2C%20ATTR%7Bqueue%2Fadd_random%7D%3D%220%22%0AACTION%3D%3D%22add%7Cchange%22%2C%20KERNEL%3D%3D%22dm-%5B0-9%5D*%22%2C%20SUBSYSTEM%3D%3D%22block%22%2C%20ENV%7BDM_NAME%7D%3D%3D%223624a937*%22%2C%20OPTIONS%3D%22nowatch%22%2C%20ATTR%7Bqueue%2Fadd_random%7D%3D%220%22%0AACTION%3D%3D%22add%7Cchange%22%2C%20KERNEL%3D%3D%22sd*%5B!0-9%5D%22%2C%20SUBSYSTEM%3D%3D%22block%22%2C%20ENV%7BID_VENDOR%7D%3D%3D%22PURE%22%2C%20OPTIONS%3D%22nowatch%22%2C%20ATTR%7Bqueue%2Frq_affinity%7D%3D%222%22%0AACTION%3D%3D%22add%7Cchange%22%2C%20KERNEL%3D%3D%22dm-%5B0-9%5D*%22%2C%20SUBSYSTEM%3D%3D%22block%22%2C%20ENV%7BDM_NAME%7D%3D%3D%223624a937*%22%2C%20OPTIONS%3D%22nowatch%22%2C%20ATTR%7Bqueue%2Frq_affinity%7D%3D%222%22%0AACTION%3D%3D%22add%7Cchange%22%2C%20KERNEL%3D%3D%22sd*%5B!0-9%5D%22%2C%20SUBSYSTEM%3D%3D%22block%22%2C%20ENV%7BID_VENDOR%7D%3D%3D%22PURE%22%2C%20OPTIONS%3D%22nowatch%22%2C%20ATTR%7Bdevice%2Ftimeout%7D%3D%2260%22Validate Configuration:
C:\oc>oc get mcp worker NAME CONFIG UPDATED UPDATING DEGRADED MACHINECOUNT READYMACHINECOUNT UPDATEDMACHINECOUNT DEGRADEDMACHINECOUNT AGE worker rendered-worker-9bff717bea674e806f07303ab142f402 True False False 3 3 3 -
Install the Portworx Operator:
-
Create a dedicated namespace to host the Portworx Operator and cluster components:
oc create namespace portworx # Verify oc get namespace portworxExpected Output:
NAME STATUS AGE portworx Active <duration> -
Install the Portworx Certified Operator into the cluster-wide openshift-operators namespace via OLM:
apiVersion: operators.coreos.com/v1alpha1 kind: Subscription metadata: name: portworx-operator namespace: openshift-operators spec: channel: stable name: portworx-certified source: certified-operators sourceNamespace: openshift-marketplace installPlanApproval: AutomaticApply the Subscription:
oc apply -f portworx-operator-subscription.yaml -
After a short time, verify that the Operator is up and that the necessary CRDs have been registered:
Check Operator Pod Status
# List Portworx operator pods in the cluster-wide operator namespace oc get pods -n openshift-operators | grep portworxYou should see something similar to:
portworx-operator-xxxxxxxxxx-xxxxx 1/1 Running 0 <duration>If the pod is not Running, inspect the logs:
oc logs -n openshift-operators deploy/portworx-operatorResolve any issues before you continue.
Verify CRDs (StorageCluster and Related Types): Confirm the StorageCluster CRD is present.
oc get crd | grep storageclusterpurestorageclusters.core.libopenstorage.org 2026-03-22T12:58:39Z storageclusters.core.libopenstorage.org 2026-03-22T12:58:18Z
-
Create a dedicated namespace to host the Portworx Operator and cluster components:
-
Integrate Portworx with FlashArray.
-
Create FlashArray API User: Create a dedicated user with storage admin privileges and generate an API token.
-
Navigate to Settings > Access > Users, create a user (e.g. px-portworx) and assign the Storage Admin role:
-
Copy and store the API token:
-
-
Create a pure.json file containing:
-
FlashArray management endpoint(s) (IP/FQDN)
-
The API token for the Portworx user
{ "FlashArrays": [ { "MgmtEndPoint": "https://<flasharray-mgmt-ip-or-fqdn>", "APIToken": "<FLASHARRAY-API-TOKEN>" } ] } -
-
Create px-pure-secret and store the pure.json file in a Kubernetes secret that Portworx can read. This secret must be created in the same namespace where your StorageCluster will live (for example, portworx). From your workstation run the following:
oc create secret generic px-pure-secret -n portworx --from-file=pure.json=./pure.json
-
Create FlashArray API User: Create a dedicated user with storage admin privileges and generate an API token.
-
Deploy StorageCluster for Everpure FlashArray.
The spec can be generated from https://central.portworx.com/specGen/px-csi-specgen:
kind: StorageCluster apiVersion: core.libopenstorage.org/v1 metadata: name: px-csi-fa-ocp-6b75a628-bda0-4db9-a226-48393159aac4 namespace: portworx annotations: portworx.io/install-source: "https://install.portworx.com/25.6?oem=px-csi&operator=true&ce=pure&csi=true&stork=false&mon=true&promop=true&kbver=1.34.2&ns=portworx&osft=true&c=px-csi-fa-ocp-6b75a628-bda0-4db9-a226-48393159aac4&r=17001&pureSanType=ISCSI&tel=true" portworx.io/is-openshift: "true" portworx.io/misc-args: "--oem px-csi" spec: image: portworx/oci-monitor:25.6.0 imagePullPolicy: Always kvdb: internal: true cloudStorage: kvdbDeviceSpec: size=32 startPort: 17001 stork: enabled: false csi: enabled: true monitoring: telemetry: enabled: true prometheus: enabled: true exportMetrics: true env: - name: PURE_FLASHARRAY_SAN_TYPE value: "ISCSI"-
Save your chosen spec as a YAML file (for example, px-flasharray-iscsi.yaml), then apply it:
oc apply -f portworx-csi.yaml -
Check the
StorageClusterstatus:oc get storagecluster -n portworx -
Monitor Portworx pods:
oc get pods -n portworxIf pods are not progressing to
Running:-
Check Operator logs:
oc logs -n openshift-operators deploy/portworx-operator -
Check an individual Portworx pod's logs:
oc logs -n portworx <portworx-pod-name>
Important:Do not proceed to creating StorageClasses or PVCs until the
StorageClusteris healthy and all Portworx pods in portworx namespace are in Running state. -
-
Save your chosen spec as a YAML file (for example, px-flasharray-iscsi.yaml), then apply it:
-
Validate with OpenShift Virtualization:
-
Create a
StorageClassthat uses Portworx as the provisioner and identifies FlashArray as the backend.For iSCSI, an example might look like:apiVersion: storage.k8s.io/v1 kind: StorageClass metadata: name: px-pure-iscsi-sc annotations: storageclass.kubernetes.io/is-default-class: "true" provisioner: pxd.portworx.com parameters: backend: pure_block allowVolumeExpansion: trueVerify the configuration:oc get storageclass -
Deploy a VM Using the FlashArray StorageClass:
-
Create a small test namespace:
oc create namespace px-test -
Create a PVC using the Portworx + FlashArray StorageClass:
apiVersion: v1 kind: PersistentVolumeClaim metadata: name: px-test-pvc namespace: px-test spec: accessModes: - ReadWriteOnce storageClassName: px-pure-iscsi-sc resources: requests: storage: 5Gioc apply -f px-test-pvc.yaml oc get pvc px-test-pvc -n px-test -wValidation:
C:\oc>oc apply -f px-test-pvc.yaml persistentvolumeclaim/px-test-pvc created C:\oc>oc get pvc px-test-pvc -n px-test -w NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS VOLUMEATTRIBUTESCLASS AGE px-test-pvc Bound pvc-08a9597a-d1db-49a5-930a-670ac895cc95 5Gi RWO px-pure-iscsi-sc <unset> 27m -
Create a simple pod that mounts it and runs I/O:
apiVersion: v1 kind: Pod metadata: name: px-test-pod namespace: px-test spec: containers: - name: app image: registry.access.redhat.com/ubi9/ubi-minimal command: ["sleep", "3600"] volumeMounts: - name: data mountPath: /data volumes: - name: data persistentVolumeClaim: claimName: px-test-pvc oc apply -f px-test-pod.yaml oc get pod px-test-pod -n px-testValidation:
C:\oc>oc get pod px-test-pod -n px-test NAME READY STATUS RESTARTS AGE px-test-pod 1/1 Running 0 14m C:\oc>oc exec -it px-test-pod -n px-test -- /bin/sh sh-5.1# df -h /data Filesystem Size Used Avail Use% Mounted on /dev/mapper/3624a93708eabcb40cc4241b20a3ed51a 4.9G 129M 4.7G 3% /data sh-5.1# ls -ld /data drwxr-xr-x. 3 root root 4096 Mar 31 13:55 /data
-
-
Create a