|
| 1 | +--- |
| 2 | +title: "Working with Benk: A storage provisioning and IO performance benchmark |
| 3 | + suite for Kubernetes." |
| 4 | +date: 2024-01-16T17:00:00.000Z |
| 5 | +author: Michael Mattsson |
| 6 | +authorimage: /img/portrait-192.jpg |
| 7 | +thumbnailimage: /img/benk-some.png |
| 8 | +disable: true |
| 9 | +tags: |
| 10 | + - kubernetes |
| 11 | + - devops |
| 12 | + - hpe-alletra |
| 13 | + - hpe-nimble-storage |
| 14 | + - hpe-3par-and-primera |
| 15 | +--- |
| 16 | +Recently Hewlett Packard Enterprise published an open source benchmark suite for storage drivers capable of dynamically provisioning persistent volumes to Kubernetes. The suite is called [Benk](https://github.com/hpe-storage/benk) and it is an acronym that plays with the word bench as in benchmark and Kubernetes. Benk is used internally at HPE for mapping performance metrics around the provisioning process itself as well as IO performance. It’s still a bit rough around the edges and not feature complete but it’s still a very useful tool to capture performance across large swaths of configurations with a high degree of automation and repeatability without too much of user attendance. |
| 17 | + |
| 18 | +A few of the features include: |
| 19 | + |
| 20 | +* Highly customizable rendering of Kubernetes resources through kustomize templates |
| 21 | +* A simple Kubernetes batch job that manage all the provisioning, decommissioning and benchmarking |
| 22 | +* Single configuration file per job that abstracts Kubernetes constructs and IO parameters |
| 23 | +* Uses industry standard FIO for filesystem benchmarking |
| 24 | +* Easy to build your own output templates using Jinja2 with the rich metrics in JSON format |
| 25 | + |
| 26 | +Let’s walk through a practical example of configuring, running and reporting a benchmark with Benk. |
| 27 | + |
| 28 | +# Prerequisites |
| 29 | + |
| 30 | +At the time of writing, parts of Benk can only be run on Mac and Linux due to a dependency on Bash. It will of course run in WSL on Windows. Besides cloning the [GitHub repo](https://github.com/hpe-storage/benk) with `git`, a recent version of `kubectl` and Python 3.x needs to be installed. |
| 31 | + |
| 32 | +Some pro-efficiency working with scripts, JSON and Kubernetes is helpful to better understand the workflows. |
| 33 | + |
| 34 | +# Hello Benk! |
| 35 | + |
| 36 | +The synopsis section of Benk on GitHub highlights the main steps to run the default job. It’s a good measure stick to understand if the lights come on and determine if the system under test is ready for a more complex job. |
| 37 | + |
| 38 | +Let’s walk through each step and explain as we go along. Descriptions are below the commands unless noted. |
| 39 | + |
| 40 | +```text |
| 41 | +git clone https://github.com/hpe-storage/benk && cd benk |
| 42 | +``` |
| 43 | + |
| 44 | +Cloning and entering the benk directory is now considered your home. Your logs will go into `logs`, your workload configurations are in `kustomize/overlays` and the Jinja2 templates are in `jinja2`. |
| 45 | + |
| 46 | +```text |
| 47 | +pip3 install -r requirements.txt |
| 48 | +``` |
| 49 | + |
| 50 | +This will install the required Python packages for the Benk `outputter.py` script. Python is not required if you don’t intend to render any output and you just want to use Benk for load generation. |
| 51 | + |
| 52 | +```text |
| 53 | +cp kustomize/base/config-dist.env kustomize/base/config.env |
| 54 | +cp kustomize/base/storageclass-dist.yaml kustomize/base/storageclass.yaml |
| 55 | +``` |
| 56 | + |
| 57 | +The two “dist” files are the base of your configuration. A `config.env` file that contains information that will be inserted into the `StorageClass` `Secret` reference. While the base configuration is heavily biased towards the HPE CSI Driver for Kubernetes, any storage driver that uses a `StorageClass` for dynamic provisioning for filesystems `Persistent Volume Claims` (PVC) can be used. Using the `storageclass.yaml` to configure your driver and pertinent details is at your discretion. |
| 58 | + |
| 59 | +```text |
| 60 | +kubectl create ns benk |
| 61 | +``` |
| 62 | + |
| 63 | +All `Namespace` resources will be provisioned into the “benk” `Namespace`. |
| 64 | + |
| 65 | +```text |
| 66 | +kubectl apply -k kustomize/overlays/default |
| 67 | +kubectl wait -n benk --for=condition=complete job/benk |
| 68 | +``` |
| 69 | + |
| 70 | +This will create the default job and wait for it to complete. It runs a 80/20 read/write random 8K workload for 30 seconds on a single replica `Deployment` using a single `PVC`. |
| 71 | + |
| 72 | +```text |
| 73 | +kubectl logs -n benk job/benk | jq |
| 74 | +``` |
| 75 | + |
| 76 | +This will render the log in pretty JSON. The log is pretty substantial and intentionally left out from the blog. An example log entry from the above job is available [on GitHub](https://github.com/hpe-storage/benk/tree/main/jinja2). |
| 77 | + |
| 78 | +There’s also tiny output template provided in the repo `jinja2/example-default.yaml.j2` that pulls some data out of the log file. |
| 79 | + |
| 80 | +```text |
| 81 | +kubectl logs -n benk job/benk | ./src/benk/outputter.py -t jinja2/example-default.yaml.j2 -l- |
| 82 | +--- |
| 83 | +report: |
| 84 | + name: Example YAML template |
| 85 | + logfile: <stdin> |
| 86 | + jobs: |
| 87 | + - threads: 1 |
| 88 | + runtime: 49s |
| 89 | + iops: 1258 |
| 90 | + bandwidth: 10MB/s |
| 91 | + bs: 8k |
| 92 | +``` |
| 93 | + |
| 94 | +Now we’ve completed the three corners of running Benk. Configured the environment and a job, ran the job successfully and distilled the results into something human readable for our demonstration. |
| 95 | + |
| 96 | +# The sequencer |
| 97 | + |
| 98 | +The core of Benk for now, is to run preconfigured jobs with kustomize. It may seem a lot of work for very little output. That’s why the repo contain two important scripts, `sequencer.sh` and `sequencer-cluster.sh`. These scripts will help you run multiple jobs and structure the output to create more meaty reports. |
| 99 | + |
| 100 | +First, copy the “default” kustomize directory into eight separate directories. |
| 101 | + |
| 102 | +```text |
| 103 | +for i in {1..8}; do cp -a kustomize/overlays/default kustomize/overlays/mytest-${i}; done |
| 104 | +``` |
| 105 | + |
| 106 | +Now, edit each `config.env` in each directory. I use `vi` to “:wn” myself through the templates. |
| 107 | + |
| 108 | +```text |
| 109 | +vi kustomize/overlays/mytest-*/config.env |
| 110 | +``` |
| 111 | + |
| 112 | +In each iteration, we’ll double `workloadThreads` so we end up with a sequence like 1, 2, 4, 8, 16, 32, 64 and 128. Wouldn’t it be useful if we could run these in sequence and report the results in the same template we used previously to understand if the system scales to when adding more threads to the workload? (The entire demo environment runs in virtual machines on decade old hardware, please don’t judge.) |
| 113 | + |
| 114 | +Run the sequence: |
| 115 | + |
| 116 | +```text |
| 117 | +./sequencer.sh mytest- |
| 118 | +``` |
| 119 | + |
| 120 | +What happens here is that the sequencer script will use globbing to find all overlays that has the prefix “mytest-”. Be mindful how you name things to avoid unexpected jobs to be executed. |
| 121 | + |
| 122 | +Next we can use the same report for all the jobs. |
| 123 | + |
| 124 | +```text |
| 125 | +./src/benk/outputter.py -l logs/run-mytest-<unique timestamp>.log -t jinja2/example-default.yaml.j2 |
| 126 | +--- |
| 127 | +report: |
| 128 | + name: Example YAML template |
| 129 | + logfile: logs/run-mytest-20240111161937.log |
| 130 | + jobs: |
| 131 | + - threads: 1 |
| 132 | + runtime: 55s |
| 133 | + iops: 1664 |
| 134 | + bandwidth: 13MB/s |
| 135 | + bs: 8k |
| 136 | + - threads: 2 |
| 137 | + runtime: 49s |
| 138 | + iops: 3291 |
| 139 | + bandwidth: 26MB/s |
| 140 | + bs: 8k |
| 141 | + - threads: 4 |
| 142 | + runtime: 48s |
| 143 | + iops: 8044 |
| 144 | + bandwidth: 63MB/s |
| 145 | + bs: 8k |
| 146 | + - threads: 8 |
| 147 | + runtime: 53s |
| 148 | + iops: 12075 |
| 149 | + bandwidth: 94MB/s |
| 150 | + bs: 8k |
| 151 | + - threads: 16 |
| 152 | + runtime: 46s |
| 153 | + iops: 16357 |
| 154 | + bandwidth: 128MB/s |
| 155 | + bs: 8k |
| 156 | + - threads: 32 |
| 157 | + runtime: 51s |
| 158 | + iops: 17284 |
| 159 | + bandwidth: 135MB/s |
| 160 | + bs: 8k |
| 161 | + - threads: 64 |
| 162 | + runtime: 48s |
| 163 | + iops: 17489 |
| 164 | + bandwidth: 137MB/s |
| 165 | + bs: 8k |
| 166 | + - threads: 128 |
| 167 | + runtime: 54s |
| 168 | + iops: 18761 |
| 169 | + bandwidth: 147MB/s |
| 170 | + bs: 8k |
| 171 | +``` |
| 172 | + |
| 173 | +We can now observe that we’re seeing diminishing returns by adding more than 16 threads to this workload. |
| 174 | + |
| 175 | +# A/B comparison |
| 176 | + |
| 177 | +A/B testing is something that has gained popularity in human interaction testing for websites. This is also a crucial testing methodology to analyze systems performance by simply changing a single parameter and rerun the exact same test to compare the outcomes. We’re in luck as Benk allows reporting on two log files at once with just a slightly different data structures being fed to the Jinja2 templates. |
| 178 | + |
| 179 | +Let’s change block size for our previous workload example. We’re going to summarize if increasing the block size will increase the bandwidth for the workload. |
| 180 | + |
| 181 | +Change the “workloadBlockSize” to “64k” with the `vi` ceremony. |
| 182 | + |
| 183 | +```text |
| 184 | +vi kustomize/overlays/mytest-*/config.env |
| 185 | +``` |
| 186 | + |
| 187 | +Re-run the sequencer. |
| 188 | + |
| 189 | +```text |
| 190 | +./sequencer.sh mytest- |
| 191 | +``` |
| 192 | + |
| 193 | +We’ll have to use a different template as Jinja2 now has to deal with managing two log files at a time. The syntax for the `outputter.py` script is also slightly different. |
| 194 | + |
| 195 | +```text |
| 196 | +./src/benk/outputter.py -a logs/run-mytest-<unique timestamp>.log -b logs/run-mytest-<unique timestamp>.log -t jinja2/example-default-ab.md.j2 |
| 197 | +``` |
| 198 | + |
| 199 | +This report template will summarize the findings in a markdown table, suitable to include in a GitHub pull request or similar to illustrate a certain discovery. |
| 200 | + |
| 201 | +```text |
| 202 | +| Threads | A (MB/s) | B (MB/s) | Diff | |
| 203 | +| ------- | -------- | -------- | ---- | |
| 204 | +| 1 | 13 | 55 | 4.2x | |
| 205 | +| 2 | 26 | 106 | 4.1x | |
| 206 | +| 4 | 63 | 181 | 2.9x | |
| 207 | +| 8 | 94 | 450 | 4.8x | |
| 208 | +| 16 | 128 | 661 | 5.2x | |
| 209 | +| 32 | 135 | 748 | 5.5x | |
| 210 | +| 64 | 137 | 840 | 6.1x | |
| 211 | +| 128 | 147 | 833 | 5.7x | |
| 212 | +``` |
| 213 | + |
| 214 | +Clearly, the performance increases nearly 5x across the board and we also have a discrepancy in the dataset. Which one? |
| 215 | + |
| 216 | +A good exercise for the reader could be to factor in latency in the report to understand the impact, which there will be as performance plateaus when you add more workload usually result in higher latency. How high? |
| 217 | + |
| 218 | +# More sequencers! |
| 219 | + |
| 220 | +There’s also a `sequencer-cluster.sh` script that allows users to orchestrate load generation across multiple clusters attached to one or many storage systems to isolate problems with high concurrency. The possibilities are quite endless. |
| 221 | + |
| 222 | +Learn more about multi-cluster scaling in [the GitHub repo](https://github.com/hpe-storage/benk#multi-cluster-testing). |
| 223 | + |
| 224 | +# Summary |
| 225 | + |
| 226 | +This hopefully gets you started on ideas you’d like to build and use cases you want to explore. The possibilities are endless. Bear in mind that Benk is by all means provided as-is and a very early implementation HPE chose to open source to better collaborate with customers and partners to isolate performance bottlenecks with full transparency. Not all advertised features in `config.env` has been implemented yet as an example and the CLI is not completed yet. |
| 227 | + |
| 228 | +HPE invites collaboration and accepts pull requests to Benk on GitHub. The [first issue](https://github.com/hpe-storage/benk/issues/2) discusses a solution on how to collapse initial configuration and reporting into a single intuitive Python CLI. |
| 229 | + |
| 230 | +Let us know what you’re building or have questions. The team behind the tool are available on HPE Developer Community Slack in the #Kubernetes channel. Sign up [here](https://developer.hpe.com/slack-signup) and sign in at [hpedev.slack.com](https://hpedev.slack.com). |
0 commit comments