2 TrueNAS CSI Storage Integration
kiyi edited this page 2026-09-02 17:18:10 -05:00

TrueNAS CSI Storage Integration

This runbook documents a reusable TrueNAS CSI configuration for dynamically provisioning NFS and iSCSI storage in an RKE2 cluster.

It is based on a validated deployment using TrueNAS SCALE 25.10 and the official TrueNAS CSI driver. Replace every placeholder with values from the target environment.

Homelab ownership and delivery status

The established CSI deployment is now represented in Homelab/platform. Its configuration examples still require capture of the exact installed release and non-secret environment values before packaging/adoption. Preserve working CSI and volumes while doing this.

The raw download/apply commands below are a historical bootstrap and controlled testing path, not the target routine delivery model. Pin a reviewed immutable upstream revision and images when preparing the UDS package; do not upgrade the live driver from a moving master URL. Routine delivery targets tested UDS packages/bundles with an explicit ownership/adoption plan.

Requirements

  • TrueNAS SCALE 25.10 or later
  • A healthy Linux-based Kubernetes or RKE2 cluster
  • Network connectivity from the cluster nodes to the TrueNAS API, NFS, and iSCSI services
  • A dedicated ZFS dataset for Kubernetes-managed storage
  • NFS and iSCSI enabled on TrueNAS
  • A TrueNAS API key sourced from OpenBao through ESO for the target deployment; preserve the existing bootstrap Secret until that integration is functional

Example storage layout:

<POOL_NAME>/
├── apps/
├── media/
└── <KUBERNETES_DATASET>/

Keeping CSI-managed storage in a dedicated dataset avoids mixing dynamically created volumes with existing application and media datasets.

Prepare worker nodes

Install the iSCSI client on each Linux worker that may mount iSCSI-backed volumes:

sudo apt update
sudo apt install -y open-iscsi
sudo systemctl enable --now iscsid

Confirm that the host also has the operating-system support required to mount NFS volumes.

Enable TrueNAS services

Enable:

  • NFS
  • iSCSI

The cluster nodes must be able to reach the configured endpoints:

TrueNAS API: <TRUENAS_IP>:<TRUENAS_API_PORT>
NFS:         <TRUENAS_IP>:2049
iSCSI:       <TRUENAS_IP>:3260

Create the TrueNAS API key

In TrueNAS 25.10, create a user-linked API key from the user menu under My API Keys.

Use a descriptive name such as:

rke2-csi

Transfer the key directly into OpenBao using a secure administrative session once the backend is functional. ESO reconciles it into the Kubernetes Secret referenced by the driver. Do not paste it into Git, chat, issue comments, logs or bundle inputs.

The platform scaffold expects Secret truenas-api-credentials in namespace truenas-csi with key api-key, sourced from the proposed KV v2 mount secret and relative path infrastructure/truenas/csi. Confirm metadata and key names against the installed driver before enabling the reference.

The existing local truenas-csi-secret.yaml is a bootstrap artifact only. Do not add the plaintext file to the repository. No plaintext upload is needed now or later; provide only sanitized metadata/key names if reconciliation is necessary. A committed ExternalSecret must contain references only, remain inactive until OpenBao/ESO works, and never import the local manifest.

Keep the working bootstrap Secret intact, validate OpenBao's independent recovery, test ESO readiness and safe ownership adoption, and verify CSI provisioning/mounts before retiring the local artifact from routine use. See Security and Secrets for the full transition and rotation procedure.

Configure and deploy the CSI driver

Download the official deployment manifest:

curl -O https://raw.githubusercontent.com/truenas/truenas-csi/master/deploy/truenas-csi-driver.yaml

Configure the driver using environment-specific values:

apiVersion: v1
kind: ConfigMap
metadata:
  name: truenas-csi-config
  namespace: truenas-csi
data:
  truenasURL: "wss://<TRUENAS_IP>:<TRUENAS_API_PORT>/api/current"
  truenasInsecure: "true"
  defaultPool: "<POOL_NAME>"
  nfsServer: "<TRUENAS_IP>"
  iscsiPortal: "<TRUENAS_IP>:3260"
  iscsiIQNBase: "iqn.2000-01.io.truenas"

Use truenasInsecure only when certificate verification must be skipped for a self-signed or otherwise locally trusted TrueNAS certificate.

The defaultPool value is the ZFS pool name. Child dataset paths are supplied by the StorageClasses.

Apply the driver:

kubectl apply -f truenas-csi-driver.yaml

If an existing deployment was updated, restart the node and controller components:

kubectl rollout restart daemonset/truenas-csi-node -n truenas-csi
kubectl rollout restart deployment/truenas-csi-controller -n truenas-csi

Verify the driver

kubectl get pods -n truenas-csi
kubectl get csidrivers

The expected healthy state is:

  • One CSI controller deployment with all containers ready
  • One CSI node pod on each Kubernetes node
  • All CSI pods in Running state

Create the NFS StorageClass

apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: truenas-nfs
provisioner: csi.truenas.io
parameters:
  protocol: nfs
  pool: "<POOL_NAME>"
  datasetPath: "<KUBERNETES_DATASET>/nfs"
  compression: LZ4
reclaimPolicy: Delete
allowVolumeExpansion: true
volumeBindingMode: Immediate

Apply it:

kubectl apply -f storageclass-nfs.yaml

Create the iSCSI StorageClass

apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: truenas-iscsi
provisioner: csi.truenas.io
parameters:
  protocol: iscsi
  pool: "<POOL_NAME>"
  datasetPath: "<KUBERNETES_DATASET>/iscsi"
  compression: LZ4
  volblocksize: "8K"
  sparse: "true"
reclaimPolicy: Delete
allowVolumeExpansion: true
volumeBindingMode: Immediate

Apply it:

kubectl apply -f storageclass-iscsi.yaml

Verify both StorageClasses:

kubectl get storageclass

Both classes should use csi.truenas.io, allow volume expansion, and use the intended reclaim and binding policies.

Validate NFS provisioning

Create a disposable ReadWriteMany claim:

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: test-nfs
spec:
  accessModes:
    - ReadWriteMany
  storageClassName: truenas-nfs
  resources:
    requests:
      storage: 1Gi

Apply and inspect it:

kubectl apply -f test-nfs-pvc.yaml
kubectl get pvc test-nfs

A Bound 1 Gi RWX claim confirms successful dynamic NFS provisioning.

Validate iSCSI provisioning

Create a disposable ReadWriteOnce claim:

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: test-iscsi
spec:
  accessModes:
    - ReadWriteOnce
  storageClassName: truenas-iscsi
  resources:
    requests:
      storage: 1Gi

Apply and watch it:

kubectl apply -f test-iscsi-pvc.yaml
kubectl get pvc test-iscsi -w

A Bound 1 Gi RWO claim confirms successful dynamic iSCSI provisioning.

After validation, remove disposable test resources according to the environment's normal cleanup process.

Storage selection guidance

truenas-nfs

Suitable for:

  • Shared application data
  • ReadWriteMany workloads
  • File-oriented storage
  • Shared content

truenas-iscsi

Suitable for:

  • Databases
  • Single-writer application state
  • Workloads requiring block-device semantics

Existing datasets are not automatically migrated into CSI-managed storage. They can be exposed separately through static NFS PersistentVolumes when appropriate.

Additional information and troubleshooting

Non-default TrueNAS HTTPS ports

The TrueNAS API may not listen on the default HTTPS port. Confirm the configured GUI HTTPS port and include it in truenasURL.

Test the API endpoint:

curl -k https://<TRUENAS_IP>:<TRUENAS_API_PORT>/api/versions

A successful response lists the supported TrueNAS API versions. TLS or WebSocket errors can indicate that the CSI URL is using the wrong HTTPS port.

iSCSI portal lookup failures

If the CSI controller reports that no iSCSI portal matches the configured address, confirm that a portal object exists in TrueNAS. Enabling the iSCSI service by itself may not create one.

In TrueNAS, review:

Shares
→ Block (iSCSI)
→ Portals

Some TrueNAS configurations offer only 0.0.0.0 as the listen address. This listens on all IPv4 interfaces and can accept connections sent to the NAS address on port 3260.

The CSI configuration should still use the address the Kubernetes nodes use to reach TrueNAS:

iscsiPortal: "<TRUENAS_IP>:3260"

The CSI driver dynamically creates targets, extents, and target/extent associations. Do not create those manually unless the deployment design specifically requires it.

Useful diagnostics

Watch a claim:

kubectl get pvc <PVC_NAME> -w

Inspect claim events:

kubectl describe pvc <PVC_NAME>

Watch recent cluster events:

kubectl get events --sort-by=.lastTimestamp -w

Inspect the CSI pods:

kubectl get pods -n truenas-csi

Watch the CSI controller during provisioning:

kubectl logs -n truenas-csi deployment/truenas-csi-controller -c csi-controller -f

Watch the CSI node plugin when troubleshooting mounts:

kubectl logs -n truenas-csi -l app=truenas-csi-node -c csi-node -f

Watch the worker's iSCSI service:

sudo journalctl -u iscsid -f

Security

Never publish or commit:

  • TrueNAS API keys
  • RKE2 node tokens
  • Kubeconfig credentials
  • SSH private keys
  • Passwords
  • Kubernetes service-account tokens

Use placeholders in all public examples.