github.com/projectcontour/contour@v1.28.2/site/content/docs/1.26/guides/global-rate-limiting.md (about) 1 --- 2 title: Global Rate Limiting 3 --- 4 5 Starting in version 1.13, Contour supports [Envoy global rate limiting][1]. 6 In global rate limiting, Envoy communicates with an external Rate Limit Service (RLS) over gRPC to make rate limit decisions for each request. 7 Envoy is configured to produce 1+ descriptors for incoming requests, containing things like the client IP, header values, and more. 8 Envoy sends descriptors to the RLS, and the RLS returns a rate limiting decision to Envoy based on the descriptors and the RLS's configured rate limits. 9 10 In this guide, we'll walk through deploying an RLS, configuring it in Contour, and configuring an `HTTPProxy` to use it for rate limiting. 11 12 **NOTE: you should not consider the RLS deployment in this guide to be production-ready.** 13 The instructions and example YAML below are intended to be a demonstration of functionality only. 14 Each user will have their own unique production requirements for their RLS deployment. 15 16 ## Prerequisites 17 18 This guide assumes that you have: 19 20 - A local KinD cluster created using [the Contour guide][2]. 21 - Contour installed and running in the cluster using the [quick start][3]. 22 23 ## Deploy an RLS 24 25 For this guide, we'll deploy the [Envoy rate limit service][4] as our RLS. 26 Per the project's README: 27 28 > The rate limit service is a Go/gRPC service designed to enable generic rate limit scenarios from different types of applications. 29 > Applications request a rate limit decision based on a domain and a set of descriptors. 30 > The service reads the configuration from disk via [runtime][10], composes a cache key, and talks to the Redis cache. 31 > A decision is then returned to the caller. 32 33 However, any service that implements the [RateLimitService gRPC interface][5] is supported by Contour/Envoy. 34 35 Create a config map with [the ratelimit service configuration][6]: 36 37 ```yaml 38 apiVersion: v1 39 kind: ConfigMap 40 metadata: 41 name: ratelimit-config 42 namespace: projectcontour 43 data: 44 ratelimit-config.yaml: | 45 domain: contour 46 descriptors: 47 48 # requests with a descriptor of ["generic_key": "foo"] 49 # are limited to one per minute. 50 - key: generic_key 51 value: foo 52 rate_limit: 53 unit: minute 54 requests_per_unit: 1 55 56 # each unique remote address (i.e. client IP) 57 # is limited to three requests per minute. 58 - key: remote_address 59 rate_limit: 60 unit: minute 61 requests_per_unit: 3 62 ``` 63 64 Create a deployment for the RLS that mounts the config map as a volume. 65 **This configuration is for demonstration purposes only and is not a production-ready deployment.** 66 ```yaml 67 apiVersion: apps/v1 68 kind: Deployment 69 metadata: 70 labels: 71 app: ratelimit 72 name: ratelimit 73 namespace: projectcontour 74 spec: 75 replicas: 1 76 selector: 77 matchLabels: 78 app: ratelimit 79 template: 80 metadata: 81 labels: 82 app: ratelimit 83 spec: 84 containers: 85 - name: redis 86 image: redis:alpine 87 env: 88 - name: REDIS_SOCKET_TYPE 89 value: tcp 90 - name: REDIS_URL 91 value: redis:6379 92 - name: ratelimit 93 image: docker.io/envoyproxy/ratelimit:6f5de117 94 ports: 95 - containerPort: 8080 96 name: http 97 protocol: TCP 98 - containerPort: 8081 99 name: grpc 100 protocol: TCP 101 volumeMounts: 102 - name: ratelimit-config 103 mountPath: /data/ratelimit/config 104 readOnly: true 105 env: 106 - name: USE_STATSD 107 value: "false" 108 - name: LOG_LEVEL 109 value: debug 110 - name: REDIS_SOCKET_TYPE 111 value: tcp 112 - name: REDIS_URL 113 value: localhost:6379 114 - name: RUNTIME_ROOT 115 value: /data 116 - name: RUNTIME_SUBDIRECTORY 117 value: ratelimit 118 - name: RUNTIME_WATCH_ROOT 119 value: "false" 120 # need to set RUNTIME_IGNOREDOTFILES to true to avoid issues with 121 # how Kubernetes mounts configmaps into pods. 122 - name: RUNTIME_IGNOREDOTFILES 123 value: "true" 124 command: ["/bin/ratelimit"] 125 livenessProbe: 126 httpGet: 127 path: /healthcheck 128 port: 8080 129 initialDelaySeconds: 5 130 periodSeconds: 5 131 volumes: 132 - name: ratelimit-config 133 configMap: 134 name: ratelimit-config 135 ``` 136 137 Create a service: 138 139 ```yaml 140 apiVersion: v1 141 kind: Service 142 metadata: 143 name: ratelimit 144 namespace: projectcontour 145 spec: 146 ports: 147 - port: 8081 148 name: grpc 149 protocol: TCP 150 selector: 151 app: ratelimit 152 type: ClusterIP 153 ``` 154 155 Check the progress of the deployment: 156 157 ```bash 158 $ kubectl -n projectcontour get pods -l app=ratelimit 159 NAME READY STATUS RESTARTS AGE 160 ratelimit-658f4b8f6b-2hnrf 2/2 Running 0 12s 161 ``` 162 163 Once the pod is `Running` with `2/2` containers ready, move onto the next step. 164 165 ## Configure the RLS with Contour 166 167 Create a Contour extension service for the RLS: 168 169 ```yaml 170 apiVersion: projectcontour.io/v1alpha1 171 kind: ExtensionService 172 metadata: 173 namespace: projectcontour 174 name: ratelimit 175 spec: 176 protocol: h2c 177 # The service name and port correspond to 178 # the service we created in the previous 179 # step. 180 services: 181 - name: ratelimit 182 port: 8081 183 timeoutPolicy: 184 response: 100ms 185 ``` 186 187 Update the Contour configmap to have the following RLS configuration: 188 189 ```yaml 190 apiVersion: v1 191 kind: ConfigMap 192 metadata: 193 name: contour 194 namespace: projectcontour 195 data: 196 contour.yaml: | 197 rateLimitService: 198 # extensionService is the <namespace>/<name> 199 # of the ExtensionService we created in the 200 # previous step. 201 extensionService: projectcontour/ratelimit 202 # domain corresponds to the domain in the 203 # projectcontour/ratelimit-config config map. 204 domain: contour 205 # failOpen is whether to allow requests through 206 # if there's an error connecting to the RLS. 207 failOpen: false 208 ``` 209 210 Restart Contour to pick up the new config map: 211 212 ```bash 213 $ kubectl -n projectcontour rollout restart deploy/contour 214 deployment.apps/contour restarted 215 ``` 216 217 ## Deploy a sample app 218 219 To demonstrate how to use global rate limiting in a `HTTPProxy` resource, we first need to deploy a simple echo application: 220 221 ```yaml 222 apiVersion: apps/v1 223 kind: Deployment 224 metadata: 225 name: ingress-conformance-echo 226 spec: 227 replicas: 1 228 selector: 229 matchLabels: 230 app.kubernetes.io/name: ingress-conformance-echo 231 template: 232 metadata: 233 labels: 234 app.kubernetes.io/name: ingress-conformance-echo 235 spec: 236 containers: 237 - name: conformance-echo 238 image: agervais/ingress-conformance-echo:latest 239 ports: 240 - name: http-api 241 containerPort: 3000 242 readinessProbe: 243 httpGet: 244 path: /health 245 port: 3000 246 --- 247 apiVersion: v1 248 kind: Service 249 metadata: 250 name: ingress-conformance-echo 251 spec: 252 ports: 253 - name: http 254 port: 80 255 targetPort: http-api 256 selector: 257 app.kubernetes.io/name: ingress-conformance-echo 258 ``` 259 260 This echo server will respond with a JSON object that reports information about the HTTP request it received, including the request headers. 261 262 Once the application is running, we can expose it to Contour with a `HTTPProxy` resource: 263 264 ```yaml 265 apiVersion: projectcontour.io/v1 266 kind: HTTPProxy 267 metadata: 268 name: echo 269 spec: 270 virtualhost: 271 fqdn: local.projectcontour.io 272 routes: 273 - conditions: 274 - prefix: / 275 services: 276 - name: ingress-conformance-echo 277 port: 80 278 - conditions: 279 - prefix: /foo 280 services: 281 - name: ingress-conformance-echo 282 port: 80 283 ``` 284 285 We can verify that the application is working by requesting any path: 286 287 ```bash 288 $ curl -k http://local.projectcontour.io/test/$((RANDOM)) 289 {"TestId":"","Path":"/test/22808","Host":"local.projectcontour.io","Method":"GET","Proto":"HTTP/1.1","Headers":{"Accept":["*/*"],"Content-Length":["0"],"User-Agent":["curl/7.75.0"],"X-Envoy-Expected-Rq-Timeout-Ms":["15000"],"X-Envoy-Internal":["true"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Proto":["http"],"X-Request-Id":["8ecb85e1-271b-44b4-9cf0-4859cbaed7a7"],"X-Request-Start":["t=1612903866.309"]}} 290 ``` 291 292 ## Add global rate limit policies 293 294 Now that we have a working application exposed by a `HTTPProxy` resource, we can add global rate limiting to it. 295 296 Edit the `HTTPProxy` that we created in the previous step to add rate limit policies to both routes: 297 298 ```yaml 299 apiVersion: projectcontour.io/v1 300 kind: HTTPProxy 301 metadata: 302 name: echo 303 spec: 304 virtualhost: 305 fqdn: local.projectcontour.io 306 routes: 307 - conditions: 308 - prefix: / 309 services: 310 - name: ingress-conformance-echo 311 port: 80 312 rateLimitPolicy: 313 global: 314 descriptors: 315 - entries: 316 - remoteAddress: {} 317 - conditions: 318 - prefix: /foo 319 services: 320 - name: ingress-conformance-echo 321 port: 80 322 rateLimitPolicy: 323 global: 324 descriptors: 325 - entries: 326 - remoteAddress: {} 327 - entries: 328 - genericKey: 329 value: foo 330 ``` 331 332 ## Default Global rate limit policy 333 334 Contour supports defining a default global rate limit policy in the `rateLimitService` configuration 335 which is applied to all virtual hosts unless the host is opted-out by 336 explicitly setting `disabled` to `true`. This is useful for a single-tenant 337 setup use-case. This means you don't have to edit all HTTPProxy objects with the same rate limit policies, instead you can 338 define the policies in the `rateLimitService` configuration like this: 339 ```yaml 340 apiVersion: v1 341 kind: ConfigMap 342 metadata: 343 name: contour 344 namespace: projectcontour 345 data: 346 contour.yaml: | 347 rateLimitService: 348 extensionService: projectcontour/ratelimit 349 domain: contour 350 failOpen: false 351 defaultGlobalRateLimitPolicy: 352 descriptors: 353 - entries: 354 - requestHeader: 355 headerName: X-Custom-Header 356 descriptorKey: CustomHeader 357 ``` 358 359 Virtual host can opt out by setting `disabled` to `true`. 360 ```yaml 361 apiVersion: projectcontour.io/v1 362 kind: HTTPProxy 363 metadata: 364 name: echo 365 spec: 366 virtualhost: 367 fqdn: local.projectcontour.io 368 rateLimitPolicy: 369 global: 370 disabled: true 371 routes: 372 - conditions: 373 - prefix: / 374 services: 375 - name: ingress-conformance-echo 376 port: 80 377 ``` 378 379 Also, the default global rate limit policy is not applied in case the virtual host defines its own global rate limit policy. 380 ```yaml 381 apiVersion: projectcontour.io/v1 382 kind: HTTPProxy 383 metadata: 384 name: echo 385 spec: 386 virtualhost: 387 fqdn: local.projectcontour.io 388 rateLimitPolicy: 389 global: 390 descriptors: 391 - entries: 392 - remoteAddress: {} 393 routes: 394 - conditions: 395 - prefix: / 396 services: 397 - name: ingress-conformance-echo 398 port: 80 399 ``` 400 401 ## Make requests 402 403 Before making requests to our `HTTPProxy`, let's quickly revisit the `ratelimit-config` config map. 404 Here's what we defined: 405 406 ```yaml 407 ... 408 descriptors: 409 # requests with a descriptor of ["generic_key": "foo"] 410 # are limited to one per minute. 411 - key: generic_key 412 value: foo 413 rate_limit: 414 unit: minute 415 requests_per_unit: 1 416 417 # each unique remote address (i.e. client IP) 418 # is limited to three total requests per minute. 419 - key: remote_address 420 rate_limit: 421 unit: minute 422 requests_per_unit: 3 423 ``` 424 425 The first entry says that requests with a descriptor of `["generic_key": "foo"]` should be limited to one per minute. 426 The second entry says that each unique remote address (client IP) should be allowed three total requests per minute. 427 All relevant rate limits are applied for each request, and requests that result in a `429 (Too Many Requests)` count against limits. 428 429 So, we should be able to make: 430 - a first request to `local.projectcontour.io/foo` that get a `200 (OK)` response 431 - a second request to `local.projectcontour.io/foo` that gets a `429 (Too Many Requests)` response (due to the first rate limit) 432 - a third request to `local.projectcontour.io/bar`that gets a `200 (OK)` response 433 - a fourth request to `local.projectcontour.io/bar`that gets a `429 (Too Many Requests)` response (due to the second rate limit) 434 435 Let's try it out (remember, you'll need to make all of these requests within 60 seconds since the rate limits are per minute): 436 437 Request #1: 438 ``` 439 $ curl -I local.projectcontour.io/foo 440 441 HTTP/1.1 200 OK 442 content-type: application/json 443 date: Mon, 08 Feb 2021 22:25:06 GMT 444 content-length: 403 445 x-envoy-upstream-service-time: 4 446 vary: Accept-Encoding 447 server: envoy 448 ``` 449 450 Request #2: 451 452 ``` 453 $ curl -I local.projectcontour.io/foo 454 455 HTTP/1.1 429 Too Many Requests 456 x-envoy-ratelimited: true 457 date: Mon, 08 Feb 2021 22:59:10 GMT 458 server: envoy 459 transfer-encoding: chunked 460 ``` 461 462 Request #3: 463 464 ``` 465 $ curl -I local.projectcontour.io/bar 466 467 HTTP/1.1 200 OK 468 content-type: application/json 469 date: Mon, 08 Feb 2021 22:59:54 GMT 470 content-length: 404 471 x-envoy-upstream-service-time: 2 472 vary: Accept-Encoding 473 server: envoy 474 ``` 475 476 Request #4: 477 478 ``` 479 $ curl -I local.projectcontour.io/bar 480 481 HTTP/1.1 429 Too Many Requests 482 x-envoy-ratelimited: true 483 date: Mon, 08 Feb 2021 23:00:28 GMT 484 server: envoy 485 transfer-encoding: chunked 486 ``` 487 488 ## Wrapping up 489 490 For more information, see the [Contour rate limiting documentation][7] and the [API reference documentation][8]. 491 492 The YAML used in this guide is available [in the Contour repository][9]. 493 494 [1]: https://www.envoyproxy.io/docs/envoy/latest/intro/arch_overview/other_features/global_rate_limiting 495 [2]: ../deploy-options/#kind 496 [3]: https://projectcontour.io/getting-started/#option-1-quickstart 497 [4]: https://github.com/envoyproxy/ratelimit 498 [5]: https://www.envoyproxy.io/docs/envoy/latest/api-v3/service/ratelimit/v3/rls.proto 499 [6]: https://github.com/envoyproxy/ratelimit#configuration 500 [7]: ../config/rate-limiting/ 501 [8]: ../config/api/ 502 [9]: {{< param github_url>}}/tree/main/examples/ratelimit 503 [10]: https://github.com/lyft/goruntime