|
| 1 | +--- |
| 2 | +title: Global Rate Limiting |
| 3 | +sidebar_position: 12 |
| 4 | +--- |
| 5 | + |
| 6 | +This document provides a step-by-step guide on how to test the global rate limiting functionality of kmesh. It covers deploying the necessary components, configuring traffic rules with an external rate limiting service, and observing the rate limiting behavior across multiple proxy instances. |
| 7 | + |
| 8 | +## Step 1. Deploy Kmesh and istiod (>=1.24) |
| 9 | + |
| 10 | +Please read [Quick Start](https://kmesh.net/docs/setup/quick-start) to complete the deployment of kmesh. |
| 11 | + |
| 12 | +## Step 2. Deploy sleep and httpbin |
| 13 | + |
| 14 | +We will deploy `httpbin` as the backend service for receiving requests and `sleep` as the client for sending requests. |
| 15 | + |
| 16 | +``` sh |
| 17 | +kubectl apply -f samples/sleep/sleep.yaml |
| 18 | +kubectl apply -f samples/httpbin/httpbin.yaml |
| 19 | +``` |
| 20 | + |
| 21 | +## Step 3. Deploy Redis for global rate limiting |
| 22 | + |
| 23 | +Global rate limiting requires an external service to coordinate rate limits across multiple proxy instances. We'll use Redis for this purpose. |
| 24 | + |
| 25 | +```sh |
| 26 | +kubectl apply -f -<<EOF |
| 27 | +apiVersion: apps/v1 |
| 28 | +kind: Deployment |
| 29 | +metadata: |
| 30 | + name: redis |
| 31 | + namespace: default |
| 32 | +spec: |
| 33 | + replicas: 1 |
| 34 | + selector: |
| 35 | + matchLabels: |
| 36 | + app: redis |
| 37 | + template: |
| 38 | + metadata: |
| 39 | + labels: |
| 40 | + app: redis |
| 41 | + spec: |
| 42 | + containers: |
| 43 | + - name: redis |
| 44 | + image: redis:7-alpine |
| 45 | + ports: |
| 46 | + - containerPort: 6379 |
| 47 | +--- |
| 48 | +apiVersion: v1 |
| 49 | +kind: Service |
| 50 | +metadata: |
| 51 | + name: redis |
| 52 | + namespace: default |
| 53 | +spec: |
| 54 | + selector: |
| 55 | + app: redis |
| 56 | + ports: |
| 57 | + - port: 6379 |
| 58 | + targetPort: 6379 |
| 59 | +EOF |
| 60 | +``` |
| 61 | + |
| 62 | +## Step 4. Deploy rate limiting service |
| 63 | + |
| 64 | +Deploy the Envoy rate limiting service that will communicate with Redis to enforce global rate limits. |
| 65 | + |
| 66 | +```sh |
| 67 | +kubectl apply -f -<<EOF |
| 68 | +apiVersion: v1 |
| 69 | +kind: ConfigMap |
| 70 | +metadata: |
| 71 | + name: ratelimit-config |
| 72 | + namespace: default |
| 73 | +data: |
| 74 | + config.yaml: | |
| 75 | + domain: httpbin-ratelimit |
| 76 | + descriptors: |
| 77 | + - key: header_match |
| 78 | + value: Service[httpbin.default]-User[none]-Id[3100861967] |
| 79 | + rate_limit: |
| 80 | + unit: second |
| 81 | + requests_per_unit: 1 |
| 82 | + - key: header_match |
| 83 | + value: Service[httpbin.default]-User[none]-Id[4123289408] |
| 84 | + rate_limit: |
| 85 | + unit: second |
| 86 | + requests_per_unit: 3 |
| 87 | + - key: generic_key |
| 88 | + value: default |
| 89 | + rate_limit: |
| 90 | + unit: second |
| 91 | + requests_per_unit: 10 |
| 92 | +--- |
| 93 | +apiVersion: apps/v1 |
| 94 | +kind: Deployment |
| 95 | +metadata: |
| 96 | + name: ratelimit |
| 97 | + namespace: default |
| 98 | +spec: |
| 99 | + replicas: 1 |
| 100 | + selector: |
| 101 | + matchLabels: |
| 102 | + app: ratelimit |
| 103 | + template: |
| 104 | + metadata: |
| 105 | + labels: |
| 106 | + app: ratelimit |
| 107 | + spec: |
| 108 | + containers: |
| 109 | + - name: ratelimit |
| 110 | + image: envoyproxy/ratelimit:master |
| 111 | + command: ["/bin/ratelimit"] |
| 112 | + env: |
| 113 | + - name: LOG_LEVEL |
| 114 | + value: debug |
| 115 | + - name: REDIS_SOCKET_TYPE |
| 116 | + value: tcp |
| 117 | + - name: REDIS_URL |
| 118 | + value: redis:6379 |
| 119 | + - name: USE_STATSD |
| 120 | + value: "false" |
| 121 | + - name: RUNTIME_ROOT |
| 122 | + value: /data |
| 123 | + - name: RUNTIME_SUBDIRECTORY |
| 124 | + value: ratelimit |
| 125 | + ports: |
| 126 | + - containerPort: 8080 |
| 127 | + - containerPort: 8081 |
| 128 | + - containerPort: 6070 |
| 129 | + volumeMounts: |
| 130 | + - name: config-volume |
| 131 | + mountPath: /data/ratelimit/config/config.yaml |
| 132 | + subPath: config.yaml |
| 133 | + readOnly: true |
| 134 | + volumes: |
| 135 | + - name: config-volume |
| 136 | + configMap: |
| 137 | + name: ratelimit-config |
| 138 | +--- |
| 139 | +apiVersion: v1 |
| 140 | +kind: Service |
| 141 | +metadata: |
| 142 | + name: ratelimit |
| 143 | + namespace: default |
| 144 | +spec: |
| 145 | + selector: |
| 146 | + app: ratelimit |
| 147 | + ports: |
| 148 | + - name: http |
| 149 | + port: 8080 |
| 150 | + targetPort: 8080 |
| 151 | + - name: grpc |
| 152 | + port: 8081 |
| 153 | + targetPort: 8081 |
| 154 | + - name: debug |
| 155 | + port: 6070 |
| 156 | + targetPort: 6070 |
| 157 | +EOF |
| 158 | +``` |
| 159 | + |
| 160 | +## Step 5. Deploy waypoint for httpbin |
| 161 | + |
| 162 | +First, if you haven't installed the Kubernetes Gateway API CRDs, run the following command to install. |
| 163 | + |
| 164 | +``` sh |
| 165 | +kubectl get crd gateways.gateway.networking.k8s.io &> /dev/null || \ |
| 166 | + { kubectl kustomize "github.com/kubernetes-sigs/gateway-api/config/crd/experimental?ref=444631bfe06f3bcca5d0eadf1857eac1d369421d" | kubectl apply -f -; } |
| 167 | +``` |
| 168 | + |
| 169 | +Next, create a dedicated Waypoint proxy for the `httpbin` service and label the service to direct its traffic through this Waypoint. |
| 170 | + |
| 171 | +```sh |
| 172 | +kmeshctl waypoint apply -n default --name httpbin-waypoint --image ghcr.io/kmesh-net/waypoint:latest |
| 173 | + |
| 174 | +kubectl label service httpbin istio.io/use-waypoint=httpbin-waypoint |
| 175 | +``` |
| 176 | + |
| 177 | +## Step 6. Deploy envoyFilter |
| 178 | + |
| 179 | +This `EnvoyFilter` resource injects a global rate-limiting filter into the `httpbin` service's Waypoint proxy. The filter is configured with the following rules: |
| 180 | + |
| 181 | +- A request with the header `quota: low` will be limited to **1 request per second** globally. |
| 182 | +- A request with the header `quota: medium` will be limited to **3 requests per second** globally. |
| 183 | +- Other requests will be subject to a default limit of **10 requests per second** globally. |
| 184 | + |
| 185 | +The `workloadSelector` ensures that this filter is applied only to the `httpbin-waypoint` proxy. |
| 186 | + |
| 187 | +```sh |
| 188 | +kubectl apply -f -<<EOF |
| 189 | +apiVersion: networking.istio.io/v1alpha3 |
| 190 | +kind: EnvoyFilter |
| 191 | +metadata: |
| 192 | + name: httpbin.global-ratelimit |
| 193 | + namespace: default |
| 194 | +spec: |
| 195 | + configPatches: |
| 196 | + - applyTo: HTTP_FILTER |
| 197 | + match: |
| 198 | + context: SIDECAR_INBOUND |
| 199 | + listener: |
| 200 | + filterChain: |
| 201 | + filter: |
| 202 | + name: envoy.filters.network.http_connection_manager |
| 203 | + subFilter: |
| 204 | + name: envoy.filters.http.router |
| 205 | + proxy: |
| 206 | + proxyVersion: ^1.* |
| 207 | + patch: |
| 208 | + operation: INSERT_BEFORE |
| 209 | + value: |
| 210 | + name: envoy.filters.http.ratelimit |
| 211 | + typed_config: |
| 212 | + '@type': type.googleapis.com/envoy.extensions.filters.http.ratelimit.v3.RateLimit |
| 213 | + domain: httpbin-ratelimit |
| 214 | + failure_mode_deny: true |
| 215 | + rate_limit_service: |
| 216 | + grpc_service: |
| 217 | + envoy_grpc: |
| 218 | + cluster_name: rate_limit_cluster |
| 219 | + transport_api_version: V3 |
| 220 | + - applyTo: CLUSTER |
| 221 | + match: |
| 222 | + context: SIDECAR_INBOUND |
| 223 | + proxy: |
| 224 | + proxyVersion: ^1.* |
| 225 | + patch: |
| 226 | + operation: ADD |
| 227 | + value: |
| 228 | + name: rate_limit_cluster |
| 229 | + type: STRICT_DNS |
| 230 | + connect_timeout: 10s |
| 231 | + lb_policy: ROUND_ROBIN |
| 232 | + http2_protocol_options: {} |
| 233 | + load_assignment: |
| 234 | + cluster_name: rate_limit_cluster |
| 235 | + endpoints: |
| 236 | + - lb_endpoints: |
| 237 | + - endpoint: |
| 238 | + address: |
| 239 | + socket_address: |
| 240 | + address: ratelimit.default.svc.cluster.local |
| 241 | + port_value: 8081 |
| 242 | + - applyTo: HTTP_ROUTE |
| 243 | + match: |
| 244 | + proxy: |
| 245 | + proxyVersion: ^1.* |
| 246 | + routeConfiguration: |
| 247 | + vhost: |
| 248 | + name: inbound|http|8000 |
| 249 | + route: |
| 250 | + name: default |
| 251 | + patch: |
| 252 | + operation: MERGE |
| 253 | + value: |
| 254 | + route: |
| 255 | + rate_limits: |
| 256 | + - actions: |
| 257 | + - header_value_match: |
| 258 | + descriptor_value: Service[httpbin.default]-User[none]-Id[3100861967] |
| 259 | + headers: |
| 260 | + - name: quota |
| 261 | + exact_match: low |
| 262 | + - actions: |
| 263 | + - header_value_match: |
| 264 | + descriptor_value: Service[httpbin.default]-User[none]-Id[4123289408] |
| 265 | + headers: |
| 266 | + - name: quota |
| 267 | + exact_match: medium |
| 268 | + - actions: |
| 269 | + - generic_key: |
| 270 | + descriptor_value: default |
| 271 | + workloadSelector: |
| 272 | + labels: |
| 273 | + gateway.networking.k8s.io/gateway-name: httpbin-waypoint |
| 274 | +EOF |
| 275 | +``` |
| 276 | + |
| 277 | +## Step 7. View the envoy filter configuration in waypoint through istioctl |
| 278 | + |
| 279 | +To verify the configuration, first get the name of the Waypoint pod, then use `istioctl` to inspect its configuration. |
| 280 | + |
| 281 | +```sh |
| 282 | +export WAYPOINT_POD=$(kubectl get pod -l gateway.networking.k8s.io/gateway-name=httpbin-waypoint -o jsonpath='{.items[0].metadata.name}') |
| 283 | +istioctl proxy-config all $WAYPOINT_POD -ojson | grep ratelimit -A 20 |
| 284 | +``` |
| 285 | + |
| 286 | +## Step 8. Find the following results, which means the configuration has been sent to waypoint |
| 287 | + |
| 288 | +```sh |
| 289 | + "envoy.filters.http.ratelimit": { |
| 290 | + "@type": "type.googleapis.com/envoy.extensions.filters.http.ratelimit.v3.RateLimit", |
| 291 | + "domain": "httpbin-ratelimit", |
| 292 | + "failure_mode_deny": true, |
| 293 | + "rate_limit_service": { |
| 294 | + "grpc_service": { |
| 295 | + "envoy_grpc": { |
| 296 | + "cluster_name": "rate_limit_cluster" |
| 297 | + } |
| 298 | + }, |
| 299 | + "transport_api_version": "V3" |
| 300 | + } |
| 301 | + } |
| 302 | +``` |
| 303 | + |
| 304 | +## Step 9. Access httpbin through sleep to see if the global rate limit is working |
| 305 | + |
| 306 | +Now, let's send requests from the `sleep` pod to the `httpbin` service to test the global rate limit rules. |
| 307 | + |
| 308 | +First, get the name of the `sleep` pod: |
| 309 | + |
| 310 | +```sh |
| 311 | +export SLEEP_POD=$(kubectl get pod -l app=sleep -o jsonpath='{.items[0].metadata.name}') |
| 312 | +``` |
| 313 | + |
| 314 | +### Test Case 1: "medium" quota |
| 315 | + |
| 316 | +The rule for `quota: medium` allows 3 requests per second globally. Rapid successive requests beyond this limit should be rate-limited. |
| 317 | + |
| 318 | +```sh |
| 319 | +kubectl exec -it $SLEEP_POD -- curl -H 'quota:medium' http://httpbin:8000/headers |
| 320 | +kubectl exec -it $SLEEP_POD -- curl -H 'quota:medium' http://httpbin:8000/headers |
| 321 | +kubectl exec -it $SLEEP_POD -- curl -H 'quota:medium' http://httpbin:8000/headers |
| 322 | +kubectl exec -it $SLEEP_POD -- curl -H 'quota:medium' http://httpbin:8000/headers |
| 323 | +``` |
| 324 | + |
| 325 | +Expected behavior: The first 3 requests should succeed, and the fourth request should be rate-limited and return an HTTP 429 status code. |
| 326 | + |
| 327 | +### Test Case 2: "low" quota |
| 328 | + |
| 329 | +The rule for `quota: low` allows only 1 request per second globally. The second request within the same second should be rate-limited. |
| 330 | + |
| 331 | +```sh |
| 332 | +kubectl exec -it $SLEEP_POD -- curl -H 'quota:low' http://httpbin:8000/headers |
| 333 | +kubectl exec -it $SLEEP_POD -- curl -H 'quota:low' http://httpbin:8000/headers |
| 334 | +``` |
| 335 | + |
| 336 | +Expected behavior: The first request should succeed, and the second request should be rate-limited and return an HTTP 429 status code. |
| 337 | + |
| 338 | +### Test Case 3: Default quota |
| 339 | + |
| 340 | +Without any quota header, requests are subject to the default limit of 10 requests per second globally. |
| 341 | + |
| 342 | +```sh |
| 343 | +for i in {1..12}; do kubectl exec -it $SLEEP_POD -- curl http://httpbin:8000/headers; done |
| 344 | +``` |
| 345 | + |
| 346 | +Expected behavior: The first 10 requests should succeed, and the 11th and 12th requests should be rate-limited. |
| 347 | + |
| 348 | +## Step 10. Verify rate limiting service logs |
| 349 | + |
| 350 | +You can check the rate limiting service logs to see the rate limiting decisions being made: |
| 351 | + |
| 352 | +```sh |
| 353 | +export RATELIMIT_POD=$(kubectl get pod -l app=ratelimit -o jsonpath='{.items[0].metadata.name}') |
| 354 | +kubectl logs $RATELIMIT_POD -f |
| 355 | +``` |
| 356 | + |
| 357 | +The logs will show rate limiting decisions and Redis interactions, providing visibility into the global rate limiting behavior. |
| 358 | + |
| 359 | +## Key Differences from Local Rate Limiting |
| 360 | + |
| 361 | +1. **Shared State**: Global rate limiting uses Redis to maintain shared state across all proxy instances, ensuring consistent rate limiting behavior regardless of which proxy handles the request. |
| 362 | + |
| 363 | +2. **External Service**: Requires deployment of a separate rate limiting service (ratelimit) that communicates with the data store. |
| 364 | + |
| 365 | +3. **Network Dependency**: Rate limiting decisions depend on network calls to the external service, which may introduce latency but provides consistency. |
| 366 | + |
| 367 | +4. **Scalability**: Better suited for distributed environments where multiple proxy instances need to coordinate rate limiting decisions. |
| 368 | + |
| 369 | +5. **Configuration**: Uses `envoy.filters.http.ratelimit` filter instead of `envoy.filters.http.local_ratelimit`, and requires additional cluster configuration for the rate limiting service. |
0 commit comments