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

follow-up(doc, fix): support for grpcroute ratelimitfilter #1766

Merged
merged 5 commits into from
Aug 9, 2023
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
268 changes: 261 additions & 7 deletions docs/latest/user/rate-limit.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,15 @@ i.e. if the data plane has 2 replicas of Envoy running, and the rate limit is 10
if 5 requests pass through the first replica and 5 requests pass through the second replica within the same second.

Envoy Gateway introduces a new CRD called [RateLimitFilter][] that allows the user to describe their rate limit intent. This instantiated resource
can be linked to a [HTTPRoute][] resource using an [ExtensionRef][] filter.
can be linked to a [HTTPRoute][] or [GRPCRoute][] resource using an [ExtensionRef][] filter.

## Prerequisites

### Install Envoy Gateway

* Follow the steps from the [Quickstart Guide](quickstart.md) to install Envoy Gateway and the example manifest.
Before proceeding, you should be able to query the example backend using HTTP.
* Follow the steps from the [Quickstart Guide](quickstart.md) to install Envoy Gateway and the HTTPRoute example manifest,
or install the GRPCRoute example from [GRPC Routing](grpc-routing.md).
shawnh2 marked this conversation as resolved.
Show resolved Hide resolved
Before proceeding, you should be able to query the example backend using HTTP/GRPC.

### Install Redis

Expand Down Expand Up @@ -143,7 +144,13 @@ spec:
limit:
requests: 3
unit: Hour
---
EOF
```

### HTTPRoute

```shell
cat <<EOF | kubectl apply -f -
apiVersion: gateway.networking.k8s.io/v1beta1
kind: HTTPRoute
metadata:
Expand Down Expand Up @@ -266,6 +273,105 @@ server: envoy

```

### GRPCRoute

```shell
cat <<EOF | kubectl apply -f -
apiVersion: gateway.networking.k8s.io/v1alpha2
kind: GRPCRoute
metadata:
name: yages
labels:
example: grpc-routing
spec:
parentRefs:
- name: example-gateway
hostnames:
- "grpc-example.com"
rules:
- matches:
- method:
method: ServerReflectionInfo
service: grpc.reflection.v1alpha.ServerReflection
- method:
method: Ping
filters:
- type: ExtensionRef
extensionRef:
group: gateway.envoyproxy.io
kind: RateLimitFilter
name: ratelimit-specific-user
backendRefs:
- group: ""
kind: Service
name: yages
port: 9000
weight: 1
EOF
```

The GRPCRoute status should indicate that it has been accepted and is bound to the example Gateway.

```shell
kubectl get grpcroute/yages -o yaml
```

Get the Gateway's address:

```shell
export GATEWAY_HOST=$(kubectl get gateway/example-gateway -o jsonpath='{.status.addresses[0].value}')
```

Lets query `grpc-example.com` with `yages.Echo/Ping` 4 times. We should receive a `pong` response from the example Gateway for the first 3 requests
and then receive a rpc error for the 4th request since the limit is set at 3 requests/Hour for the request which contains the header `x-user-id`
and value `one`.

```shell
for i in {1..4}; do grpcurl -plaintext -authority=grpc-example.com -H "x-user-id: one" ${GATEWAY_HOST}:80 yages.Echo/Ping ; sleep 1; done
```

```console
{
"text": "pong"
}

{
"text": "pong"
}

{
"text": "pong"
}

Error invoking method "yages.Echo/Ping": rpc error: code = Unavailable desc = failed to query for service descriptor "yages.Echo":

```

You should be able to send requests with the `x-user-id` header and a different value and receive successful responses from the server.

```shell
for i in {1..4}; do grpcurl -plaintext -authority=grpc-example.com -H "x-user-id: two" ${GATEWAY_HOST}:80 yages.Echo/Ping ; sleep 1; done
```

```console
{
"text": "pong"
}

{
"text": "pong"
}

{
"text": "pong"
}

{
"text": "pong"
}

```

## Rate Limit Distinct Users

Here is an example of a rate limit implemented by the application developer to limit distinct users who can be differentiated based on the
Expand All @@ -289,7 +395,13 @@ spec:
limit:
requests: 3
unit: Hour
---
EOF
```

### HTTPRoute

```shell
cat <<EOF | kubectl apply -f -
apiVersion: gateway.networking.k8s.io/v1beta1
kind: HTTPRoute
metadata:
Expand Down Expand Up @@ -318,7 +430,6 @@ spec:
EOF
```


Lets run the same command again with the header `x-user-id` and value `one` set in the request. We should the first 3 requests succeeding and
the 4th request being rate limited.

Expand Down Expand Up @@ -398,6 +509,90 @@ transfer-encoding: chunked

```

### GRPCRoute

```shell
cat <<EOF | kubectl apply -f -
apiVersion: gateway.networking.k8s.io/v1alpha2
kind: GRPCRoute
metadata:
name: yages
labels:
example: grpc-routing
spec:
parentRefs:
- name: example-gateway
hostnames:
- "grpc-example.com"
rules:
- matches:
- method:
method: ServerReflectionInfo
service: grpc.reflection.v1alpha.ServerReflection
- method:
method: Ping
filters:
- type: ExtensionRef
extensionRef:
group: gateway.envoyproxy.io
kind: RateLimitFilter
name: ratelimit-distinct-users
backendRefs:
- group: ""
kind: Service
name: yages
port: 9000
weight: 1
EOF
```

Lets run the same command again with the header `x-user-id` and value `one` set in the request. We should the first 3 requests succeeding and
the 4th request being rate limited.

```shell
for i in {1..4}; do grpcurl -plaintext -authority=grpc-example.com -H "x-user-id: one" ${GATEWAY_HOST}:80 yages.Echo/Ping ; sleep 1; done
```

```console
{
"text": "pong"
}

{
"text": "pong"
}

{
"text": "pong"
}

Error invoking method "yages.Echo/Ping": rpc error: code = Unavailable desc = failed to query for service descriptor "yages.Echo":

```

You should see the same behavior when the value for header `x-user-id` is set to `two` and 4 requests are sent.

```shell
for i in {1..4}; do grpcurl -plaintext -authority=grpc-example.com -H "x-user-id: two" ${GATEWAY_HOST}:80 yages.Echo/Ping ; sleep 1; done
```

```console
{
"text": "pong"
}

{
"text": "pong"
}

{
"text": "pong"
}

Error invoking method "yages.Echo/Ping": rpc error: code = Unavailable desc = failed to query for service descriptor "yages.Echo":

```

## Rate Limit All Requests

This example shows you how to rate limit all requests matching the HTTPRoute rule at 3 requests/Hour by leaving the `clientSelectors` field unset.
Expand All @@ -415,7 +610,13 @@ spec:
- limit:
requests: 3
unit: Hour
---
EOF
```

### HTTPRoute

```shell
cat <<EOF | kubectl apply -f -
apiVersion: gateway.networking.k8s.io/v1beta1
kind: HTTPRoute
metadata:
Expand Down Expand Up @@ -481,6 +682,58 @@ transfer-encoding: chunked

```

### GRPCRoute

```shell
cat <<EOF | kubectl apply -f -
apiVersion: gateway.networking.k8s.io/v1alpha2
kind: GRPCRoute
metadata:
name: yages
labels:
example: grpc-routing
spec:
parentRefs:
- name: example-gateway
hostnames:
- "grpc-example.com"
rules:
- matches:
- method:
method: ServerReflectionInfo
service: grpc.reflection.v1alpha.ServerReflection
- method:
method: Ping
filters:
- type: ExtensionRef
extensionRef:
group: gateway.envoyproxy.io
kind: RateLimitFilter
name: ratelimit-all-requests
backendRefs:
- group: ""
kind: Service
name: yages
port: 9000
weight: 1
EOF
```

```shell
for i in {1..4}; do grpcurl -plaintext -authority=grpc-example.com ${GATEWAY_HOST}:80 yages.Echo/Ping ; sleep 1; done
```

```console
Error invoking method "yages.Echo/Ping": rpc error: code = Unavailable desc = failed to query for service descriptor "yages.Echo":

Error invoking method "yages.Echo/Ping": rpc error: code = Unavailable desc = failed to query for service descriptor "yages.Echo":

Error invoking method "yages.Echo/Ping": rpc error: code = Unavailable desc = failed to query for service descriptor "yages.Echo":

Error invoking method "yages.Echo/Ping": rpc error: code = Unavailable desc = failed to query for service descriptor "yages.Echo":

```

## Rate Limit Client IP Addresses

Here is an example of a rate limit implemented by the application developer to limit distinct users who can be differentiated based on their
Expand Down Expand Up @@ -805,4 +1058,5 @@ kubectl rollout restart deployment envoy-gateway -n envoy-gateway-system
[Envoy Ratelimit]: https://github.com/envoyproxy/ratelimit
[EnvoyGateway]: https://gateway.envoyproxy.io/latest/api/config_types.html#envoygateway
[HTTPRoute]: https://gateway-api.sigs.k8s.io/api-types/httproute/
[GRPCRoute]: https://gateway-api.sigs.k8s.io/api-types/grpcroute/
[ExtensionRef]: https://gateway-api.sigs.k8s.io/references/spec/#gateway.networking.k8s.io%2fv1beta1.HTTPRouteFilter
9 changes: 0 additions & 9 deletions internal/provider/kubernetes/controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -417,15 +417,6 @@ func (r *gatewayAPIReconciler) findReferenceGrant(ctx context.Context, from, to
return nil, nil
}

func (r *gatewayAPIReconciler) getRateLimitFilters(ctx context.Context) ([]egv1a1.RateLimitFilter, error) {
rateLimitList := new(egv1a1.RateLimitFilterList)
if err := r.client.List(ctx, rateLimitList); err != nil {
return nil, fmt.Errorf("failed to list RateLimitFilters: %v", err)
}

return rateLimitList.Items, nil
}

func (r *gatewayAPIReconciler) processGateways(ctx context.Context, acceptedGC *gwapiv1b1.GatewayClass, resourceMap *resourceMappings, resourceTree *gatewayapi.Resources) error {
// Find gateways for the acceptedGC
// Find the Gateways that reference this Class.
Expand Down
9 changes: 9 additions & 0 deletions internal/provider/kubernetes/filters.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,15 @@
return authenList.Items, nil
}

func (r *gatewayAPIReconciler) getRateLimitFilters(ctx context.Context) ([]egv1a1.RateLimitFilter, error) {
rateLimitList := new(egv1a1.RateLimitFilterList)
if err := r.client.List(ctx, rateLimitList); err != nil {
return nil, fmt.Errorf("failed to list RateLimitFilters: %v", err)
}

Check warning on line 30 in internal/provider/kubernetes/filters.go

View check run for this annotation

Codecov / codecov/patch

internal/provider/kubernetes/filters.go#L29-L30

Added lines #L29 - L30 were not covered by tests

return rateLimitList.Items, nil
}

func (r *gatewayAPIReconciler) getExtensionRefFilters(ctx context.Context) ([]unstructured.Unstructured, error) {
var resourceItems []unstructured.Unstructured
for _, gvk := range r.extGVKs {
Expand Down
8 changes: 4 additions & 4 deletions internal/provider/kubernetes/routes.go
Original file line number Diff line number Diff line change
Expand Up @@ -177,16 +177,16 @@ func (r *gatewayAPIReconciler) processGRPCRoutes(ctx context.Context, gatewayNam
authFilter, ok := resourceMap.authenFilters[key]
shawnh2 marked this conversation as resolved.
Show resolved Hide resolved
if !ok {
r.log.Error(err, "AuthenticationFilter not found; bypassing rule", "index", i)
continue
} else {
resourceTree.AuthenticationFilters = append(resourceTree.AuthenticationFilters, authFilter)
}
resourceTree.AuthenticationFilters = append(resourceTree.AuthenticationFilters, authFilter)

rateLimitFilter, ok := resourceMap.rateLimitFilters[key]
if !ok {
r.log.Error(err, "RateLimitFilter not found; bypassing rule", "index", i)
continue
} else {
resourceTree.RateLimitFilters = append(resourceTree.RateLimitFilters, rateLimitFilter)
}
resourceTree.RateLimitFilters = append(resourceTree.RateLimitFilters, rateLimitFilter)
}
}
}
Expand Down