Skip to main content

Version: latest

Run Benchmarks on AWS EKS

This guide walks you through reproducing the published AWS EKS benchmark results on your own AWS account. The setup uses three isolated EKS node groups — one each for API7 Gateway, the NGINX upstream, and the wrk2 load generator — so that none of the three components contends with the others for CPU or network resources.

For general benchmark methodology and optimization guidance, see Performance Benchmark first. The standard production install procedure for API7 Gateway on Kubernetes is covered in Deploy on Kubernetes; this page only documents the benchmark-specific deviations.

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 and Amazon EKS node IAM role.
  • A valid API7 Enterprise license. Request a trial if you do not already have one.

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

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.

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

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 \
--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

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

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 \
--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" \
--set "apisix.replicaCount=1" \
--set "apisix.image.repository=api7/api7-ee-3-gateway" \
--set "apisix.image.tag=${GATEWAY_VERSION}" \
--set "nginx.workerProcesses=1" \
--set "apisix.nodeSelector.nodeName=api7ee" \
--set "apisix.securityContext.runAsNonRoot=false" \
--set "apisix.securityContext.runAsUser=0"

Benchmark-specific overrides compared with a normal production install:

OverridePurpose
apisix.replicaCount=1Measure single-instance throughput. Scale this up only after you have the single-instance number.
nginx.workerProcesses=1Start with a single worker process for the baseline. Raise it later to measure multi-core scaling.
apisix.nodeSelector.nodeName=api7eePin the gateway pod to the isolated benchmark node.
apisix.securityContext.runAsNonRoot=false, apisix.securityContext.runAsUser=0Run as root so that diagnostic tools (for example, perf, strace, tcpdump) can be installed inside the pod for profiling. Do not use these settings in production.
apisix.image.tag=${GATEWAY_VERSION}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 page.

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

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

The public 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:

ScenarioADC config
One route without plugins1-one-route-without-plugin.yaml
One route with limit-count2-one-route-with-limit-count.yaml
One route with key-auth + limit-count3-one-route-with-key-auth-and-limit-count.yaml
One route + one consumer with key-auth4-one-route-with-key-auth.yaml
100 routes without plugins5-100-route-without-plugin.yaml
100 routes with limit-count6-100-route-with-limit-count.yaml
100 routes + 100 consumers with key-auth + limit-count7-100-route-and-consumer-with-key-auth-limit-count.yaml
100 routes + 100 consumers with key-auth8-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

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:

wrk -t4 -c100 -d120s -R300000 \
--latency \
http://api7-ee-3-gateway.api7.svc.cluster.local:9080/get

Flags reference:

FlagMeaning
-t44 threads on the load generator
-c100100 concurrent connections
-d120s2-minute run (matches the published methodology)
-R300000Target request rate of 300,000 requests/second
--latencyEmit latency percentiles

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.

If your results deviate significantly (more than ~10%) from the published numbers, review the 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

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

API7.ai Logo

The digital world is connected by APIs,
API7.ai exists to make APIs more efficient, reliable, and secure.

Sign up for API7 newsletter

Product

API7 Gateway

SOC2 Type IIISO 27001HIPAAGDPRRed Herring

Copyright © APISEVEN PTE. LTD 2019 – 2026. Apache, Apache APISIX, APISIX, and associated open source project names are trademarks of the Apache Software Foundation