github.com/projectcontour/contour@v1.28.2/site/content/guides/global-rate-limiting.md (about)

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