github.com/projectcontour/contour@v1.28.2/site/content/docs/1.25/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 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  ## Make requests
   333  
   334  Before making requests to our `HTTPProxy`, let's quickly revisit the `ratelimit-config` config map.
   335  Here's what we defined:
   336  
   337  ```yaml
   338  ...
   339  descriptors:
   340    # requests with a descriptor of ["generic_key": "foo"]
   341    # are limited to one per minute.
   342    - key: generic_key
   343      value: foo
   344      rate_limit:
   345        unit: minute
   346        requests_per_unit: 1
   347    
   348    # each unique remote address (i.e. client IP)
   349    # is limited to three total requests per minute.
   350    - key: remote_address
   351      rate_limit:
   352        unit: minute
   353        requests_per_unit: 3
   354  ```
   355  
   356  The first entry says that requests with a descriptor of `["generic_key": "foo"]` should be limited to one per minute.
   357  The second entry says that each unique remote address (client IP) should be allowed three total requests per minute.
   358  All relevant rate limits are applied for each request, and requests that result in a `429 (Too Many Requests)` count against limits.
   359  
   360  So, we should be able to make:
   361  - a first request to `local.projectcontour.io/foo` that get a `200 (OK)` response
   362  - a second request to `local.projectcontour.io/foo` that gets a `429 (Too Many Requests)` response (due to the first rate limit)
   363  - a third request to `local.projectcontour.io/bar`that gets a `200 (OK)` response
   364  - a fourth request to `local.projectcontour.io/bar`that gets a `429 (Too Many Requests)` response (due to the second rate limit)
   365  
   366  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):
   367  
   368  Request #1:
   369  ```
   370  $ curl -I local.projectcontour.io/foo
   371  
   372  HTTP/1.1 200 OK
   373  content-type: application/json
   374  date: Mon, 08 Feb 2021 22:25:06 GMT
   375  content-length: 403
   376  x-envoy-upstream-service-time: 4
   377  vary: Accept-Encoding
   378  server: envoy
   379  ```
   380  
   381  Request #2:
   382  
   383  ```
   384  $ curl -I local.projectcontour.io/foo
   385  
   386  HTTP/1.1 429 Too Many Requests
   387  x-envoy-ratelimited: true
   388  date: Mon, 08 Feb 2021 22:59:10 GMT
   389  server: envoy
   390  transfer-encoding: chunked
   391  ```
   392  
   393  Request #3:
   394  
   395  ```
   396  $ curl -I local.projectcontour.io/bar
   397  
   398  HTTP/1.1 200 OK
   399  content-type: application/json
   400  date: Mon, 08 Feb 2021 22:59:54 GMT
   401  content-length: 404
   402  x-envoy-upstream-service-time: 2
   403  vary: Accept-Encoding
   404  server: envoy
   405  ```
   406  
   407  Request #4:
   408  
   409  ```
   410  $ curl -I local.projectcontour.io/bar
   411  
   412  HTTP/1.1 429 Too Many Requests
   413  x-envoy-ratelimited: true
   414  date: Mon, 08 Feb 2021 23:00:28 GMT
   415  server: envoy
   416  transfer-encoding: chunked
   417  ```
   418  
   419  ## Wrapping up
   420  
   421  For more information, see the [Contour rate limiting documentation][7] and the [API reference documentation][8].
   422  
   423  The YAML used in this guide is available [in the Contour repository][9].
   424  
   425  [1]: https://www.envoyproxy.io/docs/envoy/latest/intro/arch_overview/other_features/global_rate_limiting
   426  [2]: ../deploy-options/#kind
   427  [3]: https://projectcontour.io/getting-started/#option-1-quickstart
   428  [4]: https://github.com/envoyproxy/ratelimit
   429  [5]: https://www.envoyproxy.io/docs/envoy/latest/api-v3/service/ratelimit/v3/rls.proto
   430  [6]: https://github.com/envoyproxy/ratelimit#configuration
   431  [7]: ../config/rate-limiting/
   432  [8]: ../config/api/
   433  [9]: {{< param github_url>}}/tree/main/examples/ratelimit
   434  [10]: https://github.com/lyft/goruntime