Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

local ratelimit: Add descriptor support in HTTP Local Rate Limiting #14588

Merged
merged 15 commits into from
Jan 13, 2021
1 change: 1 addition & 0 deletions api/envoy/config/route/v3/route_components.proto
Original file line number Diff line number Diff line change
Expand Up @@ -1541,6 +1541,7 @@ message VirtualCluster {
}

// Global rate limiting :ref:`architecture overview <arch_overview_global_rate_limit>`.
// Also applies to Local rate limiting :ref:`using descriptors <config_http_filters_local_rate_limit_descriptors>`.
message RateLimit {
option (udpa.annotations.versioning).previous_message_type = "envoy.api.v2.route.RateLimit";

Expand Down
1 change: 1 addition & 0 deletions api/envoy/config/route/v4alpha/route_components.proto

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 9 additions & 0 deletions api/envoy/extensions/common/ratelimit/v3/ratelimit.proto
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ syntax = "proto3";
package envoy.extensions.common.ratelimit.v3;

import "envoy/type/v3/ratelimit_unit.proto";
import "envoy/type/v3/token_bucket.proto";

import "udpa/annotations/status.proto";
import "udpa/annotations/versioning.proto";
Expand Down Expand Up @@ -92,3 +93,11 @@ message RateLimitDescriptor {
// Optional rate limit override to supply to the ratelimit service.
RateLimitOverride limit = 2;
}

message LocalRateLimitDescriptor {
// Descriptor entries.
repeated v3.RateLimitDescriptor.Entry entries = 1 [(validate.rules).repeated = {min_items: 1}];

// Token Bucket algorithm for local ratelimiting.
type.v3.TokenBucket token_bucket = 2 [(validate.rules).message = {required: true}];
}
1 change: 1 addition & 0 deletions api/envoy/extensions/filters/http/local_ratelimit/v3/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ licenses(["notice"]) # Apache 2
api_proto_package(
deps = [
"//envoy/config/core/v3:pkg",
"//envoy/extensions/common/ratelimit/v3:pkg",
"//envoy/type/v3:pkg",
"@com_github_cncf_udpa//udpa/annotations:pkg",
],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ syntax = "proto3";
package envoy.extensions.filters.http.local_ratelimit.v3;

import "envoy/config/core/v3/base.proto";
import "envoy/extensions/common/ratelimit/v3/ratelimit.proto";
import "envoy/type/v3/http_status.proto";
import "envoy/type/v3/token_bucket.proto";

Expand All @@ -19,7 +20,7 @@ option (udpa.annotations.file_status).package_version_status = ACTIVE;
// Local Rate limit :ref:`configuration overview <config_http_filters_local_rate_limit>`.
// [#extension: envoy.filters.http.local_ratelimit]

// [#next-free-field: 7]
// [#next-free-field: 10]
message LocalRateLimit {
// The human readable prefix to use when emitting stats.
string stat_prefix = 1 [(validate.rules).string = {min_len: 1}];
Expand Down Expand Up @@ -67,4 +68,28 @@ message LocalRateLimit {
// have been rate limited.
repeated config.core.v3.HeaderValueOption response_headers_to_add = 6
[(validate.rules).repeated = {max_items: 10}];

// The rate limit descriptor list to use in the local rate limit to override
// on. The rate limit descriptor is selected by the first full match from the
// request descriptors.
//
// Example on how to use ::ref:`this <config_http_filters_local_rate_limit_descriptors>`
//
// .. note::
//
// In the current implementation the descriptor's token bucket :ref:`fill_interval
// <envoy_api_field_type.v3.TokenBucket.fill_interval>` must be a multiple
// global :ref:`token bucket's<envoy_api_field_extensions.filters.http.local_ratelimit.v3.LocalRateLimit.token_bucket>` fill interval.
//
// The descriptors must match verbatim for rate limiting to apply. There is no partial
// match by a subset of descriptor entries in the current implementation.
repeated common.ratelimit.v3.LocalRateLimitDescriptor descriptors = 8;

// Specifies the rate limit configurations to be applied with the same
// stage number. If not set, the default stage number is 0.
//
// .. note::
//
// The filter supports a range of 0 - 10 inclusively for stage numbers.
uint32 stage = 9 [(validate.rules).uint32 = {lte: 10}];
}
1 change: 1 addition & 0 deletions docs/root/api-v3/config/common/common.rst
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,4 @@ Common
matcher/v3/*
../../extensions/common/dynamic_forward_proxy/v3/*
../../extensions/common/tap/v3/*
../../extensions/common/ratelimit/v3/*
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,93 @@ The route specific configuration:
Note that if this filter is configured as globally disabled and there are no virtual host or route level
token buckets, no rate limiting will be applied.

.. _config_http_filters_local_rate_limit_descriptors:

Using rate limit descriptors for local rate limiting
----------------------------------------------------

Rate limit descriptors can be used to override local per-route rate limiting.
A route's :ref:`rate limit action <envoy_v3_api_msg_config.route.v3.RateLimit>`
is used to match up a :ref:`local descriptor
<envoy_v3_api_msg_extensions.common.ratelimit.v3.LocalRateLimitDescriptor>` in
the filter config descriptor list. The local descriptor's token bucket
settings are then used to decide if the request should be rate limited or not
depending on whether the local descriptor's entries match the route's rate
limit actions descriptor entries. If there is no matching descriptor entries,
the default token bucket is used.

Example filter configuration using descriptors:

.. validated-code-block:: yaml
:type-name: envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager

route_config:
name: local_route
virtual_hosts:
- name: local_service
domains: ["*"]
routes:
- match: { prefix: "/foo" }
route: { cluster: service_protected_by_rate_limit }
typed_per_filter_config:
envoy.filters.http.local_ratelimit:
"@type": type.googleapis.com/envoy.extensions.filters.http.local_ratelimit.v3.LocalRateLimit
stat_prefix: test
token_bucket:
max_tokens: 1000
tokens_per_fill: 1000
fill_interval: 60s
filter_enabled:
runtime_key: test_enabled
default_value:
numerator: 100
denominator: HUNDRED
filter_enforced:
runtime_key: test_enforced
default_value:
numerator: 100
denominator: HUNDRED
response_headers_to_add:
- append: false
header:
key: x-test-rate-limit
value: 'true'
descriptors:
- entries:
- key: client_cluster
value: foo
- key: path
value: /foo/bar
token_bucket:
max_tokens: 10
tokens_per_fill: 10
fill_interval: 60s
- entries:
- key: client_cluster
value: foo
- key: path
value: /foo/bar2
token_bucket:
max_tokens: 100
tokens_per_fill: 100
fill_interval: 60s
- match: { prefix: "/" }
route: { cluster: default_service }
rate_limits:
- actions: # any actions in here
- request_headers:
header_name: x-envoy-downstream-service-cluster
descriptor_key: client_cluster
- request_headers:
header_name: ":path"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: Should mention here that ordering of selection of descriptor is decided from the ordering of rate limit actions...

/cc @mandarjog

descriptor_key: path

In this example, requests are rate-limited for routes prefixed with "/foo" as
follow. If requests come from a downstream service cluster "foo" for "/foo/bar"
path, then 10 req/min are allowed. But if they come from a downstream service
cluster "foo" for "/foo/bar2" path, then 100 req/min are allowed. Otherwise,
1000 req/min are allowed.

Statistics
----------

Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

40 changes: 35 additions & 5 deletions include/envoy/ratelimit/ratelimit.h
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
#include "envoy/stream_info/stream_info.h"
#include "envoy/type/v3/ratelimit_unit.pb.h"

#include "absl/time/time.h"
#include "absl/types/optional.h"

namespace Envoy {
Expand All @@ -28,6 +29,15 @@ struct RateLimitOverride {
struct DescriptorEntry {
std::string key_;
std::string value_;

friend bool operator==(const DescriptorEntry& lhs, const DescriptorEntry& rhs) {
return lhs.key_ == rhs.key_ && lhs.value_ == rhs.value_;
}
template <typename H>
friend H AbslHashValue(H h, // NOLINT(readability-identifier-naming)
const DescriptorEntry& entry) {
return H::combine(std::move(h), entry.key_, entry.value_);
}
};

/**
Expand All @@ -39,21 +49,41 @@ struct Descriptor {
};

/**
* A single token bucket. See token_bucket.proto.
*/
struct TokenBucket {
uint32_t max_tokens_;
uint32_t tokens_per_fill_;
absl::Duration fill_interval_;
};

/**
* A single rate limit request descriptor. See ratelimit.proto.
*/
struct LocalDescriptor {
std::vector<DescriptorEntry> entries_;
friend bool operator==(const LocalDescriptor& lhs, const LocalDescriptor& rhs) {
kyessenov marked this conversation as resolved.
Show resolved Hide resolved
return lhs.entries_ == rhs.entries_;
}
};

/*
* Base interface for generic rate limit descriptor producer.
*/
class DescriptorProducer {
public:
virtual ~DescriptorProducer() = default;

/**
* Potentially append a descriptor entry to the end of descriptor.
* @param descriptor supplies the descriptor to optionally fill.
* Potentially fill a descriptor entry to the end of descriptor.
* @param descriptor_entry supplies the descriptor entry to optionally fill.
* @param local_service_cluster supplies the name of the local service cluster.
* @param headers supplies the header for the request.
* @param info stream info associated with the request
* @return true if the producer populated the descriptor.
*/
virtual bool populateDescriptor(Descriptor& descriptor, const std::string& local_service_cluster,
virtual bool populateDescriptor(DescriptorEntry& descriptor_entry,
const std::string& local_service_cluster,
const Http::RequestHeaderMap& headers,
const StreamInfo::StreamInfo& info) const PURE;
};
Expand All @@ -73,8 +103,8 @@ class DescriptorProducerFactory : public Config::TypedFactory {
*
* @param config supplies the configuration for the descriptor extension.
* @param validator configuration validation visitor.
* @return DescriptorProducerPtr the rate limit descriptor producer which will be used to populate
* rate limit descriptors.
* @return DescriptorProducerPtr the rate limit descriptor producer which will be used to
* populate rate limit descriptors.
*/
virtual DescriptorProducerPtr
createDescriptorProducerFromProto(const Protobuf::Message& config,
Expand Down
12 changes: 12 additions & 0 deletions include/envoy/router/router_ratelimit.h
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,18 @@ class RateLimitPolicyEntry {
const std::string& local_service_cluster,
const Http::RequestHeaderMap& headers,
const StreamInfo::StreamInfo& info) const PURE;

/**
* Potentially populate the local descriptor array with new descriptors to query.
* @param descriptors supplies the descriptor array to optionally fill.
* @param local_service_cluster supplies the name of the local service cluster.
* @param headers supplies the header for the request.
* @param info stream info associated with the request
*/
virtual void populateLocalDescriptors(std::vector<RateLimit::LocalDescriptor>& descriptors,
const std::string& local_service_cluster,
const Http::RequestHeaderMap& headers,
const StreamInfo::StreamInfo& info) const PURE;
};

/**
Expand Down
Loading