Kubernetes Production Tuning
Size, harden, and expose an API7 Gateway deployment running on Kubernetes for production workloads.
Applies to:API7 Gateway 3.10.x
Deployments:KubernetesRoles:Platform engineerSRETime:30 minutesImpact:Restarts the data plane
Goal
Tune an existing API7 Gateway Kubernetes deployment for production: size CPU and memory, add resilience with PodDisruptionBudget and HorizontalPodAutoscaler, spread pods with anti-affinity, and expose the gateway with a cloud-provider load balancer.
This guide assumes the control plane and data plane are already installed in a cluster. If you have not installed them yet, follow Deploy on Kubernetes first.
Before you begin
- A working API7 Gateway deployment in a Kubernetes cluster, installed from Deploy on Kubernetes.
kubectland Helm configured against the cluster.- Permissions to create or update
PodDisruptionBudget,HorizontalPodAutoscaler, and Service resources in the target namespace.
Set the variables this guide uses:
export API7_NAMESPACE="api7"
export GATEWAY_RELEASE="api7-ee-3-gateway"
export GATEWAY_EXTERNAL_IP="203.0.113.10" # the address the LoadBalancer Service reports after Step 4Steps
Step 1: Size CPU and worker processes
CPU requests, CPU limits, and NGINX worker processes control different things about a gateway pod, and they are configured together so that each worker has enough CPU capacity while leaving room for the other processes in the container.
If your cluster policy allows it, leave apisix.resources.limits.cpu unset, so the CPU quota never throttles gateway processes. If the policy requires a limit, set it no lower than the number of worker processes:
apisix.resources.limits.cpu >= nginx.workerProcessesEach NGINX worker is a single-threaded process, so more traffic does not let one worker use more than roughly one core, and a CPU limit above the worker count does not change that — the extra capacity is for auxiliary processes and other container overhead.
apisix.resources.requests.cpu is used for Kubernetes scheduling and resource guarantees; it is not a hard per-process limit. For workloads without sustained high load or strict performance requirements, start from one requested core per worker process:
nginx.workerProcesses | Recommended apisix.resources.requests.cpu baseline |
|---|---|
1 | 1 |
2 | 2 |
4 | 4 |
8 | 8 |
This 1:1 ratio is a practical baseline centred on worker processes, not a strict resource equation for every process in the container: auxiliary processes such as the privileged process and the cache manager consume CPU too. For high-load or performance-sensitive environments, add headroom on top of the baseline and use benchmark results and runtime monitoring to settle the final CPU request and, where one is required, the CPU limit.
Four fixed workers at the baseline:
nginx:
workerProcesses: 4
apisix:
resources:
requests:
cpu: "4"The same, where the cluster policy also requires a CPU limit, with one additional core as container-level headroom:
nginx:
workerProcesses: 4
apisix:
resources:
requests:
cpu: "4"
limits:
cpu: "5"Kubernetes accepts fractional CPU requests and limits such as 500m, but API7 Gateway uses the NGINX single-threaded worker model, so gateway performance may be constrained when each worker has well under one core. The effective number of worker processes also determines licensed DP core usage — see How Gateway Worker Processes Affect Licensed Cores.
Step 2: Set memory requests and limits
Memory is configured separately from CPU and worker processes. These are example starter values, not chart defaults:
apisix:
resources:
requests:
memory: "4Gi"
limits:
memory: "8Gi"Adjust both against your traffic profile, the plugins you enable, benchmark results and runtime monitoring. The general data plane capacity baseline is in System Requirements.
Step 3: Add pod disruption budget, autoscaling and anti-affinity
A PodDisruptionBudget keeps a gateway available through node maintenance, a HorizontalPodAutoscaler adds instances under load, and anti-affinity spreads the instances across nodes. Apply the PodDisruptionBudget directly; the autoscaler and the anti-affinity both come from the chart's values:
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: api7-gateway-pdb
namespace: api7
spec:
minAvailable: 1
selector:
matchLabels:
app.kubernetes.io/name: gatewaykubectl apply -f pdb.yamlpoddisruptionbudget.policy/api7-gateway-pdb createdautoscaling:
enabled: true
minReplicas: 2
maxReplicas: 10
targetCPUUtilizationPercentage: 70
apisix:
affinity:
podAntiAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 100
podAffinityTerm:
labelSelector:
matchExpressions:
- key: app.kubernetes.io/name
operator: In
values:
- gateway
topologyKey: kubernetes.io/hostnameThe chart renders the autoscaler only when the gateway runs as a Deployment; on a DaemonSet install it produces nothing. Autoscaling on a CPU target also requires a CPU request on the pods and a Metrics Server in the cluster — see Autoscale Data Plane on Kubernetes.
Step 4: Expose the gateway with a cloud load balancer
The gateway chart creates a NodePort Service by default, which is reachable through any node IP and is enough to verify the deployment from your own machine. On a cloud provider, switch the Service to LoadBalancer and read the address it provisions:
helm upgrade "${GATEWAY_RELEASE}" api7/gateway \
--set "gateway.type=LoadBalancer" \
-n "${API7_NAMESPACE}" --reuse-valuesSTATUS: deployedThe provider then takes a minute or two to attach an address:
kubectl -n "${API7_NAMESPACE}" get svc "${GATEWAY_RELEASE}-gateway" \
-o jsonpath='{.status.loadBalancer.ingress[0].ip}'203.0.113.10--reuse-values keeps every value from the previous install and changes only the Service type, so the upgrade replaces the Service without touching the gateway pods.
EKS
On EKS, the AWS Load Balancer Controller provisions a Network Load Balancer:
apisix:
service:
annotations:
service.beta.kubernetes.io/aws-load-balancer-type: "external"
service.beta.kubernetes.io/aws-load-balancer-nlb-target-type: "ip"
service.beta.kubernetes.io/aws-load-balancer-scheme: "internet-facing"EKS also wants the AWS Load Balancer Controller installed before you use an NLB, the EBS CSI driver add-on for PostgreSQL persistent storage, and a kubeconfig entry for the cluster:
export AWS_REGION="us-west-2" # the region your cluster is in
export EKS_CLUSTER_NAME="api7" # the name of your cluster
aws eks update-kubeconfig --region "${AWS_REGION}" --name "${EKS_CLUSTER_NAME}"Updated context arn:aws:eks:us-west-2:…:cluster/api7 in ~/.kube/configFor performance-sensitive deployments, put the two halves on dedicated node groups and schedule them with a node selector or node affinity:
| Node group | Instance type | Purpose |
|---|---|---|
api7-cp | m5.xlarge (4 vCPU, 16 GB) | Control plane components |
api7-dp | c5.2xlarge (8 vCPU, 16 GB) | Gateway data plane |
apisix:
nodeSelector:
nodeName: api7-dpGKE
GKE provisions a TCP load balancer for a LoadBalancer Service on its own. For HTTP(S) load balancing with Google Cloud Armor, or for internal-only access:
apisix:
service:
annotations:
cloud.google.com/l4-rls: "enabled"
networking.gke.io/load-balancer-type: "Internal" # For internal-only accessGKE also wants Workload Identity enabled if you use Cloud SQL for PostgreSQL, and a Cloud SQL PostgreSQL instance with a private IP in your VPC. Open the firewall ports listed in System Requirements to your GKE nodes.
AKS
AKS uses the Standard SKU load balancer by default. For internal access:
apisix:
service:
annotations:
service.beta.kubernetes.io/azure-load-balancer-internal: "true"On AKS, use the Azure Disk CSI driver — enabled by default on AKS 1.21+ — for PostgreSQL persistent storage, provision an Azure Database for PostgreSQL server for an external database, and use Azure CNI for predictable pod IP allocation in production.
Validate
Check that the data plane pods are ready, the HPA exists, and the gateway Service has an external endpoint:
kubectl -n "${API7_NAMESPACE}" get pods -l app.kubernetes.io/name=gateway
kubectl -n "${API7_NAMESPACE}" get hpa
kubectl -n "${API7_NAMESPACE}" get svc "${GATEWAY_RELEASE}-gateway"Expected: all gateway pods are Running and Ready, the HPA is OK, and the Service shows an EXTERNAL-IP when LoadBalancer is configured.
Send one request to the gateway from outside the cluster:
curl -so /dev/null -w '%{http_code}\n' "http://${GATEWAY_EXTERNAL_IP}/"Expected: 404 if no routes are configured, which proves the gateway is up and accepting traffic.
Roll back / Clean up
To remove only the production-tuning resources added in this guide:
kubectl -n "${API7_NAMESPACE}" delete poddisruptionbudget api7-gateway-pdb
kubectl -n "${API7_NAMESPACE}" delete horizontalpodautoscaler "${GATEWAY_RELEASE}-gateway"The chart names the HorizontalPodAutoscaler after the release, the same as the Service (${GATEWAY_RELEASE}-gateway) — not the literal api7-gateway-hpa. Run kubectl -n "${API7_NAMESPACE}" get hpa first if you are unsure of the exact name in your cluster.
To revert the Service to NodePort:
helm upgrade "${GATEWAY_RELEASE}" api7/gateway \
--set "gateway.type=NodePort" \
-n "${API7_NAMESPACE}" --reuse-valuesTroubleshoot
| Symptom | Cause | Fix |
|---|---|---|
| Gateway pods are throttled despite low traffic. | CPU limit is below the worker-process count. | Remove the CPU limit or set it to at least nginx.workerProcesses. |
| HPA does not scale. | Metrics server is not installed or the target Deployment label changed. | Verify kubectl top nodes works and the scaleTargetRef.name matches the gateway Deployment. |
Load balancer address stays pending. | The cloud-provider controller is not installed or lacks IAM permissions. | On EKS, confirm AWS Load Balancer Controller is running; on AKS, confirm the Azure load balancer provider is healthy. |
| Pods schedule to the same node. | Anti-affinity is preferred and no other nodes are available. | Switch to requiredDuringSchedulingIgnoredDuringExecution or add nodes. |
Related
- Deploy on Kubernetes — install the control plane and data plane before applying this guide.
- Running in Production — general production checklist and recommendations.
- Production Best Practices — security and operational best practices.
- System Requirements — hardware and software baseline.
- Helm Chart — every value accepted by the gateway chart.