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