Rate Limiting
Rate Limiting is a feature that limits request rates to protect services from overload, ensure fair resource usage, and control costs.
Table of Contents
- Overview
- Rate Limiting Types
- Local Rate Limiting
- Global Rate Limiting
- Practical Examples
- Monitoring
- Troubleshooting
Overview
Rate Limiting is needed in the following situations:
Purpose of Rate Limiting
- Service Protection: Prevent overload
- Fairness: Fair resource distribution to all clients
- Cost Control: Manage external API call costs
- Security: DDoS attack defense
Rate Limiting Types
1. Local Rate Limiting
Characteristics:
- Each Envoy proxy limits independently
- Fast response (no additional network calls)
- In distributed environments, total limit applies per instance
yaml
# 100 req/s limit per pod
# With 3 pods, up to 300 req/s total allowed2. Global Rate Limiting
Characteristics:
- Uses centralized Rate Limit server
- Accurate total limit (shared across all instances)
- Slight latency (external service call)
yaml
# 100 req/s total limit
# Only 100 req/s allowed regardless of pod countComparison
| Characteristic | Local Rate Limiting | Global Rate Limiting |
|---|---|---|
| Accuracy | Low (per instance) | High (total) |
| Performance | Very fast | Slightly slower |
| Complexity | Low | High (external service required) |
| Use Case | General protection | When precise limiting needed |
Local Rate Limiting
Token Bucket Algorithm
Basic Configuration
yaml
apiVersion: networking.istio.io/v1
kind: EnvoyFilter
metadata:
name: local-ratelimit
namespace: default
spec:
workloadSelector:
labels:
app: myapp
configPatches:
- applyTo: HTTP_FILTER
match:
context: SIDECAR_INBOUND
listener:
filterChain:
filter:
name: "envoy.filters.network.http_connection_manager"
subFilter:
name: "envoy.filters.http.router"
patch:
operation: INSERT_BEFORE
value:
name: envoy.filters.http.local_ratelimit
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.http.local_ratelimit.v3.LocalRateLimit
stat_prefix: http_local_rate_limiter
token_bucket:
max_tokens: 100 # Maximum token count
tokens_per_fill: 10 # Tokens to add
fill_interval: 1s # Fill interval
filter_enabled:
runtime_key: local_rate_limit_enabled
default_value:
numerator: 100
denominator: HUNDRED
filter_enforced:
runtime_key: local_rate_limit_enforced
default_value:
numerator: 100
denominator: HUNDRED
response_headers_to_add:
- append: false
header:
key: x-local-rate-limit
value: 'true'Key Parameters:
max_tokens: Maximum tokens the bucket can hold (allows bursts)tokens_per_fill: Tokens to add per fill_intervalfill_interval: Token addition interval
Example:
yaml
# 10 requests per second, 100 burst allowed
token_bucket:
max_tokens: 100
tokens_per_fill: 10
fill_interval: 1s
# Result:
# - Average: 10 req/s
# - Burst: 100 req/s (for short periods)Path-Based Rate Limiting
yaml
apiVersion: networking.istio.io/v1
kind: EnvoyFilter
metadata:
name: path-based-ratelimit
namespace: default
spec:
workloadSelector:
labels:
app: api-service
configPatches:
- applyTo: HTTP_FILTER
match:
context: SIDECAR_INBOUND
patch:
operation: INSERT_BEFORE
value:
name: envoy.filters.http.local_ratelimit
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.http.local_ratelimit.v3.LocalRateLimit
stat_prefix: http_local_rate_limiter
# Different limits per path
descriptors:
# /api/v1/users - High limit
- entries:
- key: header_match
value: "/api/v1/users"
token_bucket:
max_tokens: 1000
tokens_per_fill: 100
fill_interval: 1s
# /api/v1/admin - Low limit
- entries:
- key: header_match
value: "/api/v1/admin"
token_bucket:
max_tokens: 100
tokens_per_fill: 10
fill_interval: 1sHeader-Based Rate Limiting
yaml
apiVersion: networking.istio.io/v1
kind: EnvoyFilter
metadata:
name: user-based-ratelimit
namespace: default
spec:
workloadSelector:
labels:
app: api-service
configPatches:
- applyTo: HTTP_FILTER
match:
context: SIDECAR_INBOUND
patch:
operation: INSERT_BEFORE
value:
name: envoy.filters.http.local_ratelimit
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.http.local_ratelimit.v3.LocalRateLimit
stat_prefix: http_local_rate_limiter
# Limits by user tier
descriptors:
# Premium users
- entries:
- key: header_match
value: "x-user-tier:premium"
token_bucket:
max_tokens: 1000
tokens_per_fill: 100
fill_interval: 1s
# Free users
- entries:
- key: header_match
value: "x-user-tier:free"
token_bucket:
max_tokens: 100
tokens_per_fill: 10
fill_interval: 1sGlobal Rate Limiting
Global Rate Limiting uses a centralized Rate Limit service to apply accurate rate limits across the entire cluster.
Architecture
Configuration Method
Global Rate Limiting requires deploying an external Rate Limit service and integrating with EnvoyFilter.
1. Deploy Rate Limit Service
Note: Istio uses envoyproxy/ratelimit service as an external dependency.
yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: ratelimit-config
namespace: istio-system
data:
config.yaml: |
domain: production-ratelimit
descriptors:
# Global limit: 100 per second
- key: generic_key
value: "global"
rate_limit:
unit: second
requests_per_unit: 100
# Per-path limit
- key: header_match
value: "/api/v1/*"
rate_limit:
unit: second
requests_per_unit: 50
# Per-user limit (per minute)
- key: remote_address
rate_limit:
unit: minute
requests_per_unit: 1000
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: ratelimit
namespace: istio-system
spec:
replicas: 1
selector:
matchLabels:
app: ratelimit
template:
metadata:
labels:
app: ratelimit
spec:
containers:
- name: ratelimit
image: envoyproxy/ratelimit:19f2079f # Use stable version
ports:
- containerPort: 8080
name: http
- containerPort: 8081
name: grpc
env:
- name: LOG_LEVEL
value: debug
- name: RUNTIME_ROOT
value: /data
- name: RUNTIME_SUBDIRECTORY
value: ratelimit
- name: RUNTIME_IGNOREDOTFILES
value: "true"
- name: USE_STATSD
value: "false"
volumeMounts:
- name: config-volume
mountPath: /data/ratelimit/config
readOnly: true
command: ["/bin/ratelimit"]
resources:
requests:
memory: "128Mi"
cpu: "100m"
limits:
memory: "512Mi"
cpu: "500m"
volumes:
- name: config-volume
configMap:
name: ratelimit-config
---
apiVersion: v1
kind: Service
metadata:
name: ratelimit
namespace: istio-system
spec:
ports:
- port: 8080
name: http
targetPort: 8080
- port: 8081
name: grpc
targetPort: 8081
selector:
app: ratelimit2. Configure Global Rate Limiting with EnvoyFilter
yaml
apiVersion: networking.istio.io/v1alpha3
kind: EnvoyFilter
metadata:
name: filter-ratelimit
namespace: istio-system
spec:
workloadSelector:
labels:
istio: ingressgateway
configPatches:
# Add HTTP filter
- applyTo: HTTP_FILTER
match:
context: GATEWAY
listener:
filterChain:
filter:
name: "envoy.filters.network.http_connection_manager"
subFilter:
name: "envoy.filters.http.router"
patch:
operation: INSERT_BEFORE
value:
name: envoy.filters.http.ratelimit
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.http.ratelimit.v3.RateLimit
domain: production-ratelimit
failure_mode_deny: true
timeout: 5s
rate_limit_service:
grpc_service:
envoy_grpc:
cluster_name: rate_limit_cluster
transport_api_version: V3
# Add Rate Limit cluster
- applyTo: CLUSTER
match:
context: GATEWAY
patch:
operation: ADD
value:
name: rate_limit_cluster
type: STRICT_DNS
connect_timeout: 5s
lb_policy: ROUND_ROBIN
http2_protocol_options: {}
load_assignment:
cluster_name: rate_limit_cluster
endpoints:
- lb_endpoints:
- endpoint:
address:
socket_address:
address: ratelimit.istio-system.svc.cluster.local
port_value: 8081
---
apiVersion: networking.istio.io/v1alpha3
kind: EnvoyFilter
metadata:
name: filter-ratelimit-svc
namespace: istio-system
spec:
workloadSelector:
labels:
istio: ingressgateway
configPatches:
# Add rate limit action to VirtualHost
- applyTo: VIRTUAL_HOST
match:
context: GATEWAY
patch:
operation: MERGE
value:
rate_limits:
# Global limit
- actions:
- generic_key:
descriptor_value: "global"3. Add Rate Limit Actions to VirtualService
yaml
apiVersion: networking.istio.io/v1alpha3
kind: EnvoyFilter
metadata:
name: filter-ratelimit-actions
namespace: default
spec:
workloadSelector:
labels:
istio: ingressgateway
configPatches:
- applyTo: VIRTUAL_HOST
match:
context: GATEWAY
routeConfiguration:
vhost:
name: "*:80"
patch:
operation: MERGE
value:
rate_limits:
# Path-based Rate Limiting
- actions:
- header_value_match:
descriptor_value: "/api/v1/*"
headers:
- name: ":path"
string_match:
prefix: "/api/v1/"
# IP-based Rate Limiting
- actions:
- remote_address: {}Key Parameter Descriptions
| Parameter | Description |
|---|---|
domain | Rate Limit Service configuration domain (must match ConfigMap) |
failure_mode_deny | Whether to reject requests when Rate Limit Service fails |
timeout | Rate Limit Service response wait time |
rate_limit_service | External Rate Limit Service gRPC endpoint |
Global vs Local Rate Limiting Selection Criteria
Use Local Rate Limiting:
- Simple configuration
- Fast response speed
- No external dependencies
- Per-pod limits (total limit inaccurate)
Use Global Rate Limiting:
- Accurate total limits
- Complex rules (per-user, per-IP, per-path)
- Centralized management
- External service required (increased complexity)
- Slight latency (gRPC call)
Recommendations:
- Production API Gateway: Global Rate Limiting (precise control needed)
- Microservice Protection: Local Rate Limiting (fast response)
- Hybrid: Global at Gateway, Local for internal services
Practical Examples
Example 1: API Gateway Rate Limiting
yaml
apiVersion: networking.istio.io/v1
kind: EnvoyFilter
metadata:
name: api-gateway-ratelimit
namespace: istio-system
spec:
workloadSelector:
labels:
istio: ingressgateway
configPatches:
- applyTo: HTTP_FILTER
match:
context: GATEWAY
patch:
operation: INSERT_BEFORE
value:
name: envoy.filters.http.local_ratelimit
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.http.local_ratelimit.v3.LocalRateLimit
stat_prefix: http_local_rate_limiter
# Public API: Low limit
descriptors:
- entries:
- key: header_match
value: "/api/v1/public/*"
token_bucket:
max_tokens: 100
tokens_per_fill: 10
fill_interval: 1s
# Authenticated API: High limit
- entries:
- key: header_match
value: "/api/v1/protected/*"
token_bucket:
max_tokens: 1000
tokens_per_fill: 100
fill_interval: 1s
# GraphQL: Medium limit
- entries:
- key: header_match
value: "/graphql"
token_bucket:
max_tokens: 500
tokens_per_fill: 50
fill_interval: 1sExample 2: Tiered Rate Limiting by User
yaml
apiVersion: networking.istio.io/v1
kind: EnvoyFilter
metadata:
name: tiered-ratelimit
namespace: default
spec:
workloadSelector:
labels:
app: api-service
configPatches:
- applyTo: HTTP_FILTER
match:
context: SIDECAR_INBOUND
patch:
operation: INSERT_BEFORE
value:
name: envoy.filters.http.local_ratelimit
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.http.local_ratelimit.v3.LocalRateLimit
stat_prefix: http_local_rate_limiter
descriptors:
# Enterprise: 1000 req/s
- entries:
- key: header_match
value: "x-api-tier:enterprise"
token_bucket:
max_tokens: 10000
tokens_per_fill: 1000
fill_interval: 1s
# Premium: 100 req/s
- entries:
- key: header_match
value: "x-api-tier:premium"
token_bucket:
max_tokens: 1000
tokens_per_fill: 100
fill_interval: 1s
# Free: 10 req/s
- entries:
- key: header_match
value: "x-api-tier:free"
token_bucket:
max_tokens: 100
tokens_per_fill: 10
fill_interval: 1sExample 3: External API Protection
yaml
# External API call limiting (Egress)
apiVersion: networking.istio.io/v1
kind: EnvoyFilter
metadata:
name: external-api-ratelimit
namespace: default
spec:
workloadSelector:
labels:
app: myapp
configPatches:
- applyTo: HTTP_FILTER
match:
context: SIDECAR_OUTBOUND
patch:
operation: INSERT_BEFORE
value:
name: envoy.filters.http.local_ratelimit
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.http.local_ratelimit.v3.LocalRateLimit
stat_prefix: egress_rate_limiter
# External API limit (cost savings)
token_bucket:
max_tokens: 1000 # Allow burst
tokens_per_fill: 10 # 10 per second
fill_interval: 1s
# Log when limit exceeded
response_headers_to_add:
- header:
key: x-rate-limit-exceeded
value: "true"Monitoring
Prometheus Metrics
yaml
# Rate Limiting metrics
# 1. Limited request count
rate(envoy_http_local_rate_limit_rate_limited[5m])
# 2. Allowed request count
rate(envoy_http_local_rate_limit_ok[5m])
# 3. Rate Limit application rate
(rate(envoy_http_local_rate_limit_rate_limited[5m])
/
(rate(envoy_http_local_rate_limit_rate_limited[5m]) + rate(envoy_http_local_rate_limit_ok[5m]))) * 100
# 4. Global Rate Limit calls
rate(envoy_cluster_ratelimit_over_limit[5m])Grafana Dashboard
json
{
"dashboard": {
"title": "Istio Rate Limiting",
"panels": [
{
"title": "Rate Limited Requests",
"targets": [
{
"expr": "rate(envoy_http_local_rate_limit_rate_limited[5m])",
"legendFormat": "{{pod_name}}"
}
]
},
{
"title": "Rate Limit Hit Rate",
"targets": [
{
"expr": "(rate(envoy_http_local_rate_limit_rate_limited[5m]) / (rate(envoy_http_local_rate_limit_rate_limited[5m]) + rate(envoy_http_local_rate_limit_ok[5m]))) * 100",
"legendFormat": "Hit Rate %"
}
]
}
]
}
}Troubleshooting
Rate Limiting Not Working
bash
# 1. Check EnvoyFilter
kubectl get envoyfilter -A
# 2. Check Envoy configuration
istioctl proxy-config listeners <pod-name> -n <namespace> -o json | \
jq '.[] | select(.name | contains("0.0.0.0")) | .filterChains[].filters[] | select(.name == "envoy.filters.network.http_connection_manager") | .typedConfig.httpFilters[] | select(.name == "envoy.filters.http.local_ratelimit")'
# 3. Check logs
kubectl logs -n <namespace> <pod-name> -c istio-proxy | grep ratelimitGlobal Rate Limiting Connection Failure
bash
# Check Rate Limit Service
kubectl get pods -n istio-system -l app=ratelimit
kubectl logs -n istio-system -l app=ratelimit
# Check Redis connection
kubectl exec -n istio-system -it deploy/ratelimit -- redis-cli -h redis-ratelimit ping