Skip to main content
Version: Dev

Deploy AISIX Gateways on Kubernetes

Use the api7/aisix Helm chart to deploy, expose, and scale AISIX gateways in your Kubernetes cluster. The gateways serve live AI traffic in your environment and connect to an existing AISIX Cloud control plane for configuration.

The chart requires the data-plane manager endpoint and a gateway certificate bundle issued for the target environment. Once connected, the gateways receive models, caller API keys, and policies from the control plane.

Prerequisites

  • A Kubernetes cluster with kubectl configured to access it and Helm 3 installed.
  • Access to an AISIX Cloud environment where you can issue a gateway certificate.
  • Network access from the cluster to the AISIX Cloud data-plane manager endpoint. If you use On-Premises, complete On-Premises Installation first.

Install the Chart

In the console, open the target environment's Data planes view and issue a gateway certificate. The Kubernetes (Helm) tab provides the data-plane manager endpoint and certificate bundle used below. The example also pins the initial deployment to one replica. The private key is shown only once.

Store the bundle in a Secret so the private key stays out of your values file:

kubectl create namespace aisix

kubectl -n aisix create secret generic aisix-gateway-certificate \
--from-file=cert.pem=./cert.pem \
--from-file=key.pem=./key.pem \
--from-file=ca.pem=./ca.pem

Install the chart against the data-plane manager endpoint from the same view. The Dev documentation overrides the chart's released gateway image with the dev image:

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

helm install aisix api7/aisix --namespace aisix \
--set image.repository=ghcr.io/api7/aisix \
--set image.tag=dev \
--set controlPlane.baseURL=https://dp-manager.example.com:7944 \
--set controlPlane.certificate.existingSecret=aisix-gateway-certificate \
--set replicaCount=1

The gateway appears in the environment's Data planes view once its first heartbeat lands. The chart defaults to two replicas, but this initial command starts one because rate-limit counters use per-replica memory until you configure shared Redis. The console Helm snippet omits replicaCount, so installing that snippet unchanged starts two replicas. Add --set replicaCount=1 until Redis is configured, as shown above. Share Rate-Limit Counters across Replicas before increasing the replica count or enabling an autoscaler.

Each replica registers as its own instance, and they all share the one certificate.

To see every value the chart accepts:

helm show values api7/aisix

The chart source is published in the api7/api7-helm-chart repository.

Expose the Gateway

The chart creates a ClusterIP Service for the proxy by default. To publish it through a cloud load balancer:

service:
type: LoadBalancer
port: 80
# Preserve the client source IP, which per-model IP allowlists match on.
externalTrafficPolicy: Local

The gateway binds port 3000 inside the container by default. A Service can expose port 80 or 443 without making the process bind a privileged container port.

Bind a port below 1024 inside the container only when the gateway must listen on that port directly. The published image runs as non-root UID 10001, and the gateway binary carries the effective CAP_NET_BIND_SERVICE file capability:

containerPorts:
proxy: 80

If you customize the rendered Pod to drop all capabilities, add NET_BIND_SERVICE back to the AISIX container:

spec:
containers:
- name: aisix
securityContext:
capabilities:
drop: ["ALL"]
add: ["NET_BIND_SERVICE"]

The Kubernetes Restricted Pod Security Standard allows this capability. Because the binary's file capability has the effective bit set, the container can fail with exec: Operation not permitted when the runtime prevents the capability from being granted.

Use hostNetwork or hostPort only when the gateway must bind directly on a node and the cluster policy allows it. These options introduce node-port conflicts and reduce network isolation; the Baseline and Restricted Pod Security Standards also disallow them.

See Network and Security for the full exposure and credential model.

Share Rate-Limit Counters across Replicas

Rate-limit counters live in each gateway's own memory by default, so N replicas enforce N times every configured request, token, and concurrency limit. Before running more than one replica — including any replica an autoscaler adds — point every replica at one Redis:

rateLimit:
backend: redis
redis:
url: redis://redis.default.svc:6379

Use rateLimit.redis.existingSecret instead when the connection URL carries a password.

After every replica points to the same Redis deployment, increase replicaCount or enable one of the autoscaling options below. If the deployment does not use request, token, or concurrency limits, you can deliberately accept the per-replica memory backend instead.

Scale on CPU or Memory

autoscaling creates a HorizontalPodAutoscaler for the gateway Deployment:

autoscaling:
enabled: true
minReplicas: 2
maxReplicas: 20
targetCPUUtilizationPercentage: 70

The targets are percentages of the pod's resource requests, so the chart sets a CPU request by default. It deliberately sets no CPU limit: throttling adds tail latency to a proxy and suppresses the signal the autoscaler reads. Scaling on CPU requires metrics-server in the cluster.

On Linux, proxy.workers defaults to the CPU parallelism available to the gateway process. A Kubernetes CPU request does not constrain that value, but a CPU limit does. Because the chart sets no CPU limit, set AISIX_PROXY__WORKERS explicitly when each replica needs a stable worker count, and keep it within the CPU capacity planned for the replica. See Thread-per-Core Workers for worker configuration and sizing considerations.

When autoscaling is enabled, the Deployment omits spec.replicas so that a later helm upgrade cannot reset the replica count the autoscaler chose. The replicaCount value is ignored from then on.

Pass any behavior policy through unchanged, for example to make scale-down gentler than the Kubernetes default:

autoscaling:
behavior:
scaleDown:
stabilizationWindowSeconds: 300
policies:
- type: Pods
value: 1
periodSeconds: 60

Use autoscaling.extraMetrics for Pods, Object, or External metrics such as a series exposed through a Prometheus adapter.

Scale on Request Load with KEDA

CPU is a proxy for load. To scale on the gateway's own traffic instead, use KEDA and a Prometheus query over the gateway metrics.

Before enabling the example, prepare these cluster components:

  • Install KEDA, including the ScaledObject CRD and controller.
  • Provide a Prometheus server that can query the gateway metrics.
  • If the chart should create the scrape configuration shown below, install Prometheus Operator or another controller that consumes ServiceMonitor objects. Installing only the CRD lets Helm create the object, but nothing turns it into scrape configuration. Set metrics.serviceMonitor.labels when the Prometheus instance selects ServiceMonitors by label. If Prometheus discovers the metrics Service another way, leave metrics.serviceMonitor.enabled set to false and omit that block.

Confirm the CRDs required by the values you enable:

kubectl get crd scaledobjects.keda.sh
# Required only when metrics.serviceMonitor.enabled is true.
kubectl get crd servicemonitors.monitoring.coreos.com

Configure the chart after those prerequisites are available:

metrics:
serviceMonitor:
enabled: true

keda:
enabled: true
minReplicas: 2
maxReplicas: 20
pollingInterval: 15
cooldownPeriod: 300
triggers:
- type: prometheus
metadata:
serverAddress: http://prometheus.monitoring.svc:9090
query: sum(rate(aisix_llm_requests_total[2m]))
threshold: "100"

aisix_llm_requests_total counts model-inference requests, such as /v1/chat/completions. It does not include MCP or A2A calls. Use aisix_proxy_requests_total if that is the load you want to scale on.

autoscaling and keda are mutually exclusive. Enabling both fails the Helm render rather than letting two controllers write spec.replicas.

What Happens during a Scaling Event

A replica the autoscaler adds does not receive traffic before it can serve it. Until the gateway has applied configuration from the control plane it does not bind the proxy listener at all, so every probe the chart points at that listener is refused rather than answered.

The chart's startupProbe is what covers that wait. Kubernetes holds the readiness and liveness probes back until a startup probe succeeds, so during the wait the readiness probe does not run and the pod simply never becomes ready — Kubernetes keeps it out of the Service endpoints, and liveness does not restart a pod that is still reaching the control plane. The startup probe's budget is periodSeconds x failureThreshold, and it has to cover reaching the control plane and applying what it holds. A pod whose control-plane connection stays unreachable for longer than that budget has its container killed and restarted, and repeated restarts show as CrashLoopBackOff — the intended outcome for an instance that has never had anything to serve. See Startup and the First Configuration for the readiness contract and how to size the budget.

When a replica scales down, Kubernetes starts removing it from Service endpoints and terminating the pod concurrently. Two chart values protect in-flight requests while that happens.

preStopSleepSeconds pauses before the container receives SIGTERM, so endpoint removal reaches every node before the gateway begins shutting down. That covers a balancer watching the Kubernetes API; one that polls a health check instead sees a still-ready pod throughout the pause and is covered by the gateway's own drain window, shutdown.min_drain_secs.

terminationGracePeriodSeconds caps the whole sequence — the pause, the drain window, and the in-flight drain that follows — because the gateway drains without a deadline of its own. The Kubernetes default of 30 seconds would cut a streaming response, so the chart ships a longer one.

Both values ship with defaults sized for that sequence; helm show values api7/aisix reports what they currently are. Raise terminationGracePeriodSeconds if your workloads stream for longer than the budget it leaves once the pause and the drain window have run.

To watch scaling decisions and the metrics behind them:

kubectl -n aisix get hpa aisix --watch
kubectl -n aisix describe hpa aisix

Survive Node Disruption

A PodDisruptionBudget keeps voluntary disruptions — node drains, cluster upgrades — from taking every gateway down at once. Spread constraints keep replicas out of a single failure domain:

podDisruptionBudget:
enabled: true
minAvailable: 50%

topologySpreadConstraints:
- maxSkew: 1
topologyKey: topology.kubernetes.io/zone
whenUnsatisfiable: ScheduleAnyway
labelSelector:
matchLabels:
app.kubernetes.io/name: aisix

The chart uses an emptyDir volume for /var/lib/aisix, so its state is ephemeral and scoped to one pod. A restarted pod therefore re-registers and downloads its configuration from the control plane before receiving traffic. Keep multiple replicas across failure domains so an existing replica can continue serving during a restart. If a gateway must restart from cached configuration while the control plane is unavailable, use a customized workload that gives each replica its own persistent state directory.

See High Availability for the wider deployment pattern.

Set Any Other Gateway Configuration

The control plane owns dynamic resources, and the chart owns the pod. For startup configuration the chart does not surface as a value, set the environment variable directly — every configuration field is reachable as AISIX_<SECTION>__<FIELD>:

extraEnvVars:
- name: AISIX_OBSERVABILITY__LOG_LEVEL
value: "debug"
- name: AISIX_UPSTREAM__POOL_MAX_IDLE_PER_HOST
value: "32"

See Environment Variables for the naming rules.