πŸ“šBook Signing at KubeCon EU 2026Meet us at Booking.com HQ (Mon 18:30-21:00) & vCluster booth #521 (Tue 24 Mar, 12:30-1:30pm) β€” free book giveaway!RSVP Booking.com Event
Storage intermediate ⏱ 15 minutes K8s 1.28+

K8s-IO Benchmark CLI for fio and HammerDB

Run distributed fio and HammerDB storage benchmarks on Kubernetes with K8s-IO, a lightweight Go CLI tool that replaces heavy benchmark operators.

By Luca Berton β€’ β€’ πŸ“– 7 min read

πŸ’‘ Quick Answer: Run distributed fio and HammerDB benchmarks on Kubernetes with K8s-IO β€” a lightweight Go CLI that replaces heavy operator-based solutions. Config-driven with YAML, supports multiple samples, block sizes, and CSV result export.

The Problem

Running storage and database benchmarks on Kubernetes traditionally requires deploying a full operator (like benchmark-operator) with cluster-wide CRDs and RBAC. For quick benchmark runs across different StorageClasses, you need something lighter β€” a CLI tool you can run from your laptop that creates benchmark pods, collects results, and cleans up.

The Solution

What is K8s-IO?

K8s-IO is a Go-based CLI tool by Joe Taleric (Red Hat) for running benchmark workloads on Kubernetes. It reuses Jinja templates from the benchmark-operator project but without any operator overhead.

Supported benchmarks:

  • fio β€” Distributed I/O benchmark (storage performance)
  • HammerDB β€” Database benchmark (PostgreSQL, MariaDB, MSSQL)

Install K8s-IO

# Clone and build
git clone https://github.com/jtaleric/k8s-io.git
cd k8s-io
go mod tidy
go build -o k8s-io .

# Or cross-compile
GOOS=linux GOARCH=amd64 go build -o k8s-io-linux .
GOOS=darwin GOARCH=amd64 go build -o k8s-io-macos .

# Move to PATH
sudo mv k8s-io /usr/local/bin/

fio Random I/O Config

# config-fio-random.yaml
namespace: "benchmark-fio"
workload:
  name: "fio"
  args:
    kind: "pod"                    # "pod" or "vm" (KubeVirt)
    servers: 3                     # Number of FIO server pods
    samples: 3                     # Test iterations for statistical accuracy
    jobs: ["randread", "randwrite", "randrw"]  # Random I/O patterns
    bs: ["4KiB", "8KiB"]          # Block sizes
    numjobs: [4, 8]               # FIO processes per pod
    filesize: "4G"                 # File size per job
    storageclass: "ocs-storagecluster-ceph-rbd"  # Target StorageClass
    storagesize: "50Gi"           # PVC size
    fio_path: "/data"             # Test directory
    prefill: true                  # Prefill files before testing

fio Sequential I/O Config

# config-fio-sequential.yaml
namespace: "benchmark-fio-seq"
workload:
  name: "fio"
  args:
    kind: "pod"
    servers: 3
    samples: 3
    jobs: ["read", "write"]        # Sequential patterns
    bs: ["1MiB", "4MiB"]          # Large block sizes for throughput
    numjobs: [1, 4]
    filesize: "8G"
    storageclass: "ocs-storagecluster-ceph-rbd"
    storagesize: "100Gi"
    fio_path: "/data"
    prefill: true

# Optional: Prometheus metrics collection
prometheus:
  url: "http://prometheus.monitoring.svc.cluster.local:9091"

Run Benchmarks

# Run distributed fio β€” random I/O
./k8s-io -config config-fio-random.yaml

# Run distributed fio β€” sequential I/O
./k8s-io -config config-fio-sequential.yaml

# Dry-run: generate manifests without applying
./k8s-io -config config-fio-random.yaml -dry-run

# Cleanup after benchmark
./k8s-io -config config-fio-random.yaml -cleanup

Understanding fio Results

K8s-IO automatically parses fio JSON output and displays formatted results:

=== FIO Benchmark Results ===
Test ID                    Sample Job  Hostname      Read IOPS  Read BW   Write IOPS Write BW   Read P50  Read P95  Write P50 Write P95 Runtime
-------                    ------ ---  --------      ---------  -------   ---------- --------   --------  --------  --------- --------- -------
17586514_randread_4KiB_3   1      read worker-node-1 8284.2     33136     0.0        0          95.7      236.5     0.0       0.0       60
17586514_randread_4KiB_3   1      read worker-node-2 8105.7     32422     0.0        0          96.8      244.7     0.0       0.0       60
17586514_randread_4KiB_3   1      read worker-node-3 8291.1     33164     0.0        0          95.7      236.5     0.0       0.0       60
17586514_randread_4KiB_3   2      read worker-node-1 8545.0     34180     0.0        0          93.0      230.0     0.0       0.0       60

Results are also exported to CSV:

Results exported to: fio-results-17586514_randread_4KiB_3-20260408-120000.csv

Key Metrics Explained

MetricWhat It MeansGood Values
Read IOPSRandom read operations/sec>10K (SSD), >50K (NVMe)
Write IOPSRandom write operations/sec>5K (SSD), >30K (NVMe)
Read BW (KB/s)Sequential read throughput>500 MB/s (NVMe)
Write BW (KB/s)Sequential write throughput>300 MB/s (NVMe)
Read Lat P50 (Β΅s)Median read latency<200Β΅s (NVMe), <1ms (SSD)
Read Lat P95 (Β΅s)95th percentile read latency<500Β΅s (NVMe), <5ms (SSD)

HammerDB Database Benchmark

# config-hammerdb.yaml
namespace: "benchmark-hammerdb"
workload:
  name: "hammerdb"
  args:
    kind: "pod"
    db_type: "pg"                  # "pg", "mariadb", "mssql"
    db_init: true                  # Initialize TPC-C schema
    db_benchmark: true             # Run benchmark
    db_server: "postgresql.default.svc.cluster.local"
    warehouses: 10                 # TPC-C warehouses (scale factor)
    virtual_users: 5               # Concurrent users
    duration: 10                   # Test duration (minutes)
# Run database benchmark
./k8s-io -config config-hammerdb.yaml

Compare Storage Classes

# Test Ceph RBD
cat > config-fio-rbd.yaml << 'EOF'
namespace: "bench-rbd"
workload:
  name: "fio"
  args:
    kind: "pod"
    servers: 3
    samples: 3
    jobs: ["randread", "randwrite"]
    bs: ["4KiB"]
    numjobs: [4]
    filesize: "4G"
    storageclass: "ocs-storagecluster-ceph-rbd"
    storagesize: "50Gi"
    prefill: true
EOF

# Test CephFS
cat > config-fio-cephfs.yaml << 'EOF'
namespace: "bench-cephfs"
workload:
  name: "fio"
  args:
    kind: "pod"
    servers: 3
    samples: 3
    jobs: ["randread", "randwrite"]
    bs: ["4KiB"]
    numjobs: [4]
    filesize: "4G"
    storageclass: "ocs-storagecluster-cephfs"
    storagesize: "50Gi"
    prefill: true
EOF

# Test local NVMe
cat > config-fio-local.yaml << 'EOF'
namespace: "bench-local"
workload:
  name: "fio"
  args:
    kind: "pod"
    servers: 3
    samples: 3
    jobs: ["randread", "randwrite"]
    bs: ["4KiB"]
    numjobs: [4]
    filesize: "4G"
    storageclass: "local-nvme"
    storagesize: "50Gi"
    prefill: true
EOF

# Run all three
./k8s-io -config config-fio-rbd.yaml
./k8s-io -config config-fio-cephfs.yaml
./k8s-io -config config-fio-local.yaml

# Compare CSV results
paste <(head -1 fio-results-*rbd*.csv) <(head -1 fio-results-*cephfs*.csv)

KubeVirt VM Benchmarks

K8s-IO also supports running fio inside KubeVirt VMs:

# config-fio-vm.yaml
namespace: "benchmark-fio-vm"
workload:
  name: "fio"
  args:
    kind: "vm"                     # Run inside KubeVirt VMs
    servers: 2
    samples: 2
    jobs: ["randread", "randwrite"]
    bs: ["4KiB"]
    numjobs: [4]
    filesize: "4G"
    storageclass: "ocs-storagecluster-ceph-rbd"
    fio_path: "/test"              # Default for VMs is /test
    prefill: true

Prometheus Integration

# Add Prometheus config for metric collection during benchmarks
prometheus:
  url: "http://prometheus.monitoring.svc.cluster.local:9091"
  # token: "optional-bearer-token"  # Auto-creates if omitted

This enables correlating fio results with:

  • Storage backend CPU/memory usage
  • Network throughput between pods and storage
  • Ceph OSD latency and queue depth
  • Node-level I/O wait metrics

K8s-IO vs Alternatives

ToolTypeOverheadBenchmarksResults
K8s-IOCLINone β€” runs from laptopfio, HammerDBTable + CSV
benchmark-operatorOperatorCRDs + controller podfio, HammerDB, iperf3, uperfElasticsearch
dbenchPodSingle podfio onlyLogs
Custom JobsYAMLManual setupAnyManual
graph TD
    A[k8s-io -config config-fio-random.yaml] --> B[Parse YAML config]
    B --> C[Render Jinja templates β†’ K8s manifests]
    C --> D[Create namespace + PVCs]
    D --> E[Deploy fio server pods]
    E --> F[Run fio client with jobs Γ— block sizes Γ— samples]
    F --> G[Collect JSON results from each pod]
    G --> H[Parse & format table output]
    H --> I[Export CSV with timestamps]
    I --> J[k8s-io -cleanup β†’ remove all resources]

Common Issues

IssueCauseFix
PVC pendingStorageClass doesn’t existkubectl get sc β€” use correct name
Pods stuck creatingImage pull issuesCheck node internet access
Low IOPS resultsPrefill not enabledSet prefill: true
Permission deniedOpenShift SCCUse privileged SCC for benchmark ns
Results vary between samplesNot enough samplesIncrease samples: 5 for stability
Build failsGo version too oldRequires Go 1.21+

Best Practices

  • Run at least 3 samples β€” single runs are noisy, use statistics
  • Enable prefill β€” first-write penalty skews results without prefill
  • Test multiple block sizes β€” 4K for IOPS, 1M for throughput
  • Compare StorageClasses β€” run identical configs against each backend
  • Use dry-run first β€” verify generated manifests before applying
  • Save CSV results β€” track performance over time and across upgrades
  • Cleanup after tests β€” ./k8s-io -cleanup removes all benchmark resources
  • Use Prometheus integration β€” correlate fio results with backend metrics

Key Takeaways

  • K8s-IO is a lightweight Go CLI for Kubernetes storage and database benchmarking
  • No operator installation needed β€” runs from your laptop with kubeconfig access
  • Config-driven: YAML files define benchmark parameters, easily version-controlled
  • Automatic result parsing with formatted table output and CSV export
  • Supports both pods and KubeVirt VMs as benchmark targets
  • Reuses battle-tested templates from the benchmark-operator project
#k8s-io #fio #hammerdb #benchmark #storage-performance
Luca Berton
Written by Luca Berton

Principal Solutions Architect specializing in Kubernetes, AI/GPU infrastructure, and cloud-native platforms. Author of Kubernetes Recipes and creator of CopyPasteLearn courses.

Kubernetes Recipes book cover

Want More Kubernetes Recipes?

This recipe is from Kubernetes Recipes, our 750-page practical guide with hundreds of production-ready patterns.

Luca Berton Ansible Pilot Ansible by Example Open Empower K8s Recipes Terraform Pilot CopyPasteLearn ProteinLens