# Run Benchmarks on AWS EKS

This guide walks you through reproducing the [published AWS EKS benchmark results](https://docs.api7.ai/api7-gateway/configure-and-manage/performance-benchmark.md#published-results--aws-eks) on your own AWS account. The setup uses three isolated EKS node groups: one for API7 Gateway, one for the NGINX upstream, and one for the `wrk2` load generator. This separation prevents CPU and network contention between the components.

For general benchmark methodology and optimization guidance, see [Performance Benchmark](https://docs.api7.ai/api7-gateway/configure-and-manage/performance-benchmark.md) first. The standard production install procedure for API7 Gateway on Kubernetes is covered in [Deploy on Kubernetes](https://docs.api7.ai/api7-gateway/install/deploy-on-kubernetes.md); this page only documents the benchmark-specific deviations.

## Prerequisites[​](#prerequisites "Direct link to Prerequisites")

* An AWS account with permission to create EKS clusters and EC2 instances.
* `kubectl` and `helm` installed locally, at versions compatible with your target EKS cluster.
* An EKS cluster service role and an EKS node IAM role. See [Amazon EKS cluster IAM role](https://docs.aws.amazon.com/eks/latest/userguide/service_IAM_role.html#create-service-role) and [Amazon EKS node IAM role](https://docs.aws.amazon.com/eks/latest/userguide/create-node-role.html).
* A valid API7 Enterprise license. [Request a trial](https://api7.ai/try?product=enterprise) if you do not already have one.

## Step 1: Create the EKS cluster[​](#step-1-create-the-eks-cluster "Direct link to Step 1: Create the EKS cluster")

In the AWS Console, create a new EKS cluster:

1. **Add cluster** in the EKS console.
2. Attach the EKS cluster service role you created in the prerequisites.
3. Configure VPC and subnets so that the three node groups will be able to reach each other on the cluster network.
4. Configure observability (CloudWatch logging) as you prefer.
5. Enable the cluster add-ons you normally run (for example, VPC CNI and CoreDNS).
6. Create the cluster and wait for it to reach the `ACTIVE` state.

## Step 2: Create three isolated node groups[​](#step-2-create-three-isolated-node-groups "Direct link to Step 2: Create three isolated node groups")

The benchmark requires three separate EC2 node groups running `c5.4xlarge` instances so that API7 Gateway, the upstream, and `wrk2` each have 16 vCPUs and 32 GB RAM without contending with the others.

For each node group:

1. Open **Compute > Add node group** in the EKS console.
2. Give the node group a name (for example, `api7ee`, `upstream`, or `wrk2`).
3. Attach the EKS node IAM role.
4. Select **Amazon Linux 2 (AL2\_x86\_64)** as the AMI type.
5. Select **c5.4xlarge** as the instance type.
6. Set the desired size to at least 1 node.
7. Enable SSH access if you want to run diagnostic commands on the node.
8. Save and wait for the node group to reach `ACTIVE`.

Repeat until you have three node groups: **api7ee**, **upstream**, and **wrk2**.

caution

Do not use burstable instance families (such as `t3` or `t4g`) for benchmarks. Their credit-based CPU model produces non-repeatable results. See [Avoid burstable cloud instances](https://docs.api7.ai/api7-gateway/configure-and-manage/performance-benchmark.md#avoid-burstable-cloud-instances).

## Step 3: Connect `kubectl` and label the nodes[​](#step-3-connect-kubectl-and-label-the-nodes "Direct link to step-3-connect-kubectl-and-label-the-nodes")

Configure `kubectl` to talk to the new cluster:

```
aws eks update-kubeconfig --region <region-code> --name <your-cluster-name>
```

Verify the connection:

```
kubectl get svc
```

Expected output (the `kubernetes` service is created automatically):

```
NAME         TYPE        CLUSTER-IP   EXTERNAL-IP   PORT(S)   AGE

kubernetes   ClusterIP   10.100.0.1   <none>        443/TCP   39m
```

Label one node in each node group so that Kubernetes can schedule pods onto them deterministically:

```
kubectl get nodes



kubectl label nodes <api7ee-node-name>  nodeName=api7ee

kubectl label nodes <upstream-node-name> nodeName=upstream

kubectl label nodes <wrk2-node-name>    nodeName=wrk2
```

## Step 4: Install API7 Enterprise on the api7ee node[​](#step-4-install-api7-enterprise-on-the-api7ee-node "Direct link to Step 4: Install API7 Enterprise on the api7ee node")

Create the namespace and install the API7 Enterprise control plane Helm chart, pinning every CP component (Dashboard, PostgreSQL, Prometheus) to the `api7ee` node.

```
kubectl create namespace api7



helm repo add api7 https://charts.api7.ai

helm repo update



helm install api7ee3 api7/api7ee3 \

  --version "~3.10.0" \

  --set nodeSelector."nodeName"=api7ee \

  --set postgresql.primary.nodeSelector."nodeName"=api7ee \

  --set prometheus.server.nodeSelector."nodeName"=api7ee \

  -n api7
```

note

The bundled PostgreSQL and Prometheus enable persistent storage by default. If your cluster does not have a default `StorageClass` and you want to keep the benchmark environment stateless, add `--set postgresql.primary.persistence.enabled=false` and `--set prometheus.server.persistence.enabled=false` to the command above.

Wait for the control plane pods to be ready:

```
kubectl -n api7 get pods -l app.kubernetes.io/name=api7ee3 -w
```

Port-forward the Dashboard and upload your license:

```
kubectl -n api7 port-forward svc/api7ee3-dashboard 7443:7443
```

Open `https://localhost:7443`, log in with the default credentials (`admin`/`admin`), and upload the license from **Organization > Settings > License**.

In the Dashboard, go to **Gateway Settings** and set the Control Plane address to `https://api7ee3-dp-manager:7943` so that the data plane you install next can connect to it.

### Disable the global Prometheus plugin[​](#disable-the-global-prometheus-plugin "Direct link to Disable the global Prometheus plugin")

The benchmark measures raw request-processing performance, so the global `prometheus` plugin should be disabled for the test run — otherwise every request incurs the Prometheus metric-recording cost. Disable the global `prometheus` plugin from the Dashboard before you start the benchmark.

## Step 5: Install API7 Gateway on the api7ee node[​](#step-5-install-api7-gateway-on-the-api7ee-node "Direct link to Step 5: Install API7 Gateway on the api7ee node")

In the Dashboard, click **Add Gateway Instance** and select **Kubernetes** to generate the install script. The dashboard generates three certificates (`tls.crt`, `tls.key`, `ca.crt`) and the Helm command you need to run.

Save the certificates and create the Kubernetes secret:

```
kubectl create secret generic -n api7 api7-ee-3-gateway-tls \

  --from-file=tls.crt=/tmp/tls.crt \

  --from-file=tls.key=/tmp/tls.key \

  --from-file=ca.crt=/tmp/ca.crt
```

Install the gateway chart with the benchmark-specific overrides:

```
helm upgrade --install -n api7 --create-namespace api7-ee-3-gateway api7/gateway \

  --version "~3.10.0" \

  --set "etcd.auth.tls.enabled=true" \

  --set "etcd.auth.tls.existingSecret=api7-ee-3-gateway-tls" \

  --set "etcd.auth.tls.certFilename=tls.crt" \

  --set "etcd.auth.tls.certKeyFilename=tls.key" \

  --set "etcd.auth.tls.verify=true" \

  --set "gateway.tls.existingCASecret=api7-ee-3-gateway-tls" \

  --set "gateway.tls.certCAFilename=ca.crt" \

  --set "apisix.extraEnvVars[0].name=API7_GATEWAY_GROUP_SHORT_ID" \

  --set "apisix.extraEnvVars[0].value=default" \

  --set "etcd.host[0]=https://api7ee3-dp-manager:7943" \

  // Annotate 1

  --set "apisix.replicaCount=1" \

  --set "apisix.image.repository=api7/api7-ee-3-gateway" \

  // Annotate 2

  --set "apisix.image.tag=${GATEWAY_VERSION}" \

  // Annotate 3

  --set "nginx.workerProcesses=1" \

  // Annotate 4

  --set "apisix.nodeSelector.nodeName=api7ee" \

  // Annotate 5

  --set "apisix.securityContext.runAsNonRoot=false" \

  --set "apisix.securityContext.runAsUser=0"
```

❶ Measure single-instance throughput. Scale this up only after you have the single-instance number.

❷ Pin the data plane image to the version that matches your control plane release. Look up the exact tag on Docker Hub or in the [Supported Versions](https://docs.api7.ai/api7-gateway/install/supported-versions-and-interoperability.md) page.

❸ Start with a single worker process for the baseline. Raise it later to measure multi-core scaling.

❹ Pin the gateway pod to the isolated benchmark node.

❺ Run as root so that diagnostic tools such as `perf`, `strace`, and `tcpdump` can be installed inside the pod for profiling. **Do not use these settings in production.**

## Step 6: Deploy the NGINX upstream on the upstream node[​](#step-6-deploy-the-nginx-upstream-on-the-upstream-node "Direct link to Step 6: Deploy the NGINX upstream on the upstream node")

The upstream is a minimal NGINX server that returns a 200-byte static response. Access logging is disabled so that upstream disk I/O does not become the bottleneck.

Create `nginx-upstream.yaml`:

nginx-upstream.yaml

```
apiVersion: v1

kind: ConfigMap

metadata:

  namespace: api7

  name: nginx-config

data:

  nginx.conf: |

    master_process on;

    worker_processes 1;

    events {

      worker_connections 4096;

    }

    http {

      access_log off;

      server_tokens off;

      keepalive_requests 10000000;

      server {

        listen 1980;

        server_name _;

        location / {

          return 200 "hello world\n";

        }

      }

    }

---

apiVersion: apps/v1

kind: Deployment

metadata:

  namespace: api7

  name: nginx-deployment

spec:

  replicas: 1

  selector:

    matchLabels:

      app: nginx

  template:

    metadata:

      labels:

        app: nginx

    spec:

      nodeSelector:

        nodeName: upstream

      containers:

        - name: nginx

          image: nginx:1.25.4

          volumeMounts:

            - name: nginx-config

              mountPath: /etc/nginx/nginx.conf

              subPath: nginx.conf

      volumes:

        - name: nginx-config

          configMap:

            name: nginx-config

---

apiVersion: v1

kind: Service

metadata:

  namespace: api7

  name: nginx-upstream

spec:

  selector:

    app: nginx

  ports:

    - port: 1980

      targetPort: 1980
```

Apply it:

```
kubectl apply -f nginx-upstream.yaml
```

## Step 7: Deploy `wrk2` on the wrk2 node[​](#step-7-deploy-wrk2-on-the-wrk2-node "Direct link to step-7-deploy-wrk2-on-the-wrk2-node")

`wrk2` is the load generator. Pin it to the `wrk2` node so that it does not share CPU with either the gateway or the upstream.

Create `wrk2.yaml`:

wrk2.yaml

```
apiVersion: apps/v1

kind: Deployment

metadata:

  namespace: api7

  name: wrk2-deployment

spec:

  replicas: 1

  selector:

    matchLabels:

      app: wrk2

  template:

    metadata:

      labels:

        app: wrk2

    spec:

      nodeSelector:

        nodeName: wrk2

      containers:

        - name: wrk2

          image: bootjp/wrk2

          command: ["sleep", "infinity"]
```

Apply it:

```
kubectl apply -f wrk2.yaml
```

`exec` into the pod when you are ready to run the load generator:

```
kubectl -n api7 exec -it deploy/wrk2-deployment -- sh
```

## Step 8: Apply the test scenario configurations[​](#step-8-apply-the-test-scenario-configurations "Direct link to Step 8: Apply the test scenario configurations")

The public [api7-gateway-performance-benchmark](https://github.com/api7/api7-gateway-performance-benchmark) repository contains ready-to-use ADC configurations for every scenario in the benchmark table. Clone the repository and apply each scenario in sequence using the [ADC CLI](https://docs.api7.ai/api7-gateway/reference/adc.md):

| Scenario                                                   | ADC config                                                                                                                                                                                         |
| ---------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| One route without plugins                                  | [`1-one-route-without-plugin.yaml`](https://github.com/api7/api7-gateway-performance-benchmark/blob/main/adc_conf/1-one-route-without-plugin.yaml)                                                 |
| One route with `limit-count`                               | [`2-one-route-with-limit-count.yaml`](https://github.com/api7/api7-gateway-performance-benchmark/blob/main/adc_conf/2-one-route-with-limit-count.yaml)                                             |
| One route with `key-auth` + `limit-count`                  | [`3-one-route-with-key-auth-and-limit-count.yaml`](https://github.com/api7/api7-gateway-performance-benchmark/blob/main/adc_conf/3-one-route-with-key-auth-and-limit-count.yaml)                   |
| One route + one consumer with `key-auth`                   | [`4-one-route-with-key-auth.yaml`](https://github.com/api7/api7-gateway-performance-benchmark/blob/main/adc_conf/4-one-route-with-key-auth.yaml)                                                   |
| 100 routes without plugins                                 | [`5-100-route-without-plugin.yaml`](https://github.com/api7/api7-gateway-performance-benchmark/blob/main/adc_conf/5-100-route-without-plugin.yaml)                                                 |
| 100 routes with `limit-count`                              | [`6-100-route-with-limit-count.yaml`](https://github.com/api7/api7-gateway-performance-benchmark/blob/main/adc_conf/6-100-route-with-limit-count.yaml)                                             |
| 100 routes + 100 consumers with `key-auth` + `limit-count` | [`7-100-route-and-consumer-with-key-auth-limit-count.yaml`](https://github.com/api7/api7-gateway-performance-benchmark/blob/main/adc_conf/7-100-route-and-consumer-with-key-auth-limit-count.yaml) |
| 100 routes + 100 consumers with `key-auth`                 | [`8-100-route-and-consumer-with-key-auth.yaml`](https://github.com/api7/api7-gateway-performance-benchmark/blob/main/adc_conf/8-100-route-and-consumer-with-key-auth.yaml)                         |

Apply a scenario with `adc sync`:

```
adc sync \

  --server https://<dashboard-host>:7443 \

  --token <your-api7-api-key> \

  --gateway-group default \

  -f 1-one-route-without-plugin.yaml
```

## Step 9: Run the load test[​](#step-9-run-the-load-test "Direct link to Step 9: Run the load test")

From inside the `wrk2` pod, run the load generator against the API7 Gateway service. Target the internal service DNS name so that traffic stays on the cluster network:

```
// Annotate 1

wrk -t4 -c100 -d120s -R300000 \

  // Annotate 2

  --latency \

  http://api7-ee-3-gateway.api7.svc.cluster.local:9080/get
```

❶ Use 4 threads, 100 concurrent connections, a 2-minute run, and a target rate of 300,000 requests/second to match the published methodology.

❷ Emit latency percentiles in the output.

Run each test case 5 times and average the results. Record QPS, P95, and P99 for each scenario, then compare against the published numbers in [Performance Benchmark](https://docs.api7.ai/api7-gateway/configure-and-manage/performance-benchmark.md#published-results--aws-eks).

If your results deviate significantly (more than \~10%) from the published numbers, review the [Optimization recommendations](https://docs.api7.ai/api7-gateway/configure-and-manage/performance-benchmark.md#optimization-recommendations) and verify that:

* Access logs are disabled on both API7 Gateway and the NGINX upstream.
* `ulimit -n` is at least `1024000` on all three nodes.
* The global `prometheus` plugin is disabled.
* No other pods are scheduled on the three benchmark nodes.
* Error logs for API7 Gateway are clean (no upstream DNS errors, no connection failures).

## Clean up[​](#clean-up "Direct link to Clean up")

Delete the test namespace when you are done:

```
kubectl delete namespace api7
```

Then delete the three node groups and the EKS cluster from the AWS Console to stop EC2 charges.

## Next steps[​](#next-steps "Direct link to Next steps")

* [Performance Benchmark](https://docs.api7.ai/api7-gateway/configure-and-manage/performance-benchmark.md) — methodology, published results, and optimization guidance.
* [Scale the Data Plane](https://docs.api7.ai/api7-gateway/configure-and-manage/scale-data-plane.md) — apply what you learned from benchmarking to a production scaling plan.
* [Deploy on Kubernetes](https://docs.api7.ai/api7-gateway/install/deploy-on-kubernetes.md) — the standard production install procedure.
