diff --git a/README.md b/README.md index e2f45e36f..304e835d2 100644 --- a/README.md +++ b/README.md @@ -7,6 +7,7 @@ - [Deprecation Schedule](#deprecation-schedule) - [Building and Testing](#building-and-testing) - [Docker-compose setup](#docker-compose-setup) + - [Full test environment](#full-test-environment) - [Configuration](#configuration) - [The configuration format](#the-configuration-format) - [Definitions](#definitions) @@ -110,6 +111,23 @@ If you want to run with [two redis instances](#two-redis-instances), you will ne the docker-compose.yaml file to run a second redis container, and change the environment variables as explained in the [two redis instances](#two-redis-instances) section. +## Full test environment +To run a fully configured environment to demo Envoy based rate limiting, run: +```bash +docker-compose -f docker-compose-example.yaml up +``` +This will run ratelimit, redis, prom-statsd-exporter and two Envoy containers such that you can demo rate limiting by hitting the below endpoints. +```bash +curl localhost:8888/test +curl localhost:8888/header -H "foo: foo" # Header based +curl localhost:8888/twoheader -H "foo: foo" -H "bar: bar" # Two headers +curl localhost:8888/twoheader -H "foo: foo" -H "baz: baz" +curl localhost:8888/twoheader -H "foo: foo" -H "bar: banned" # Ban a particular header value +``` +Edit `examples/ratelimit/config/example.yaml` to test different rate limit configs. Hot reloading is enabled. + +The descriptors in `example.yaml` and the actions in `examples/envoy/proxy.yaml` should give you a good idea on how to configure rate limits. + # Configuration ## The configuration format @@ -325,7 +343,7 @@ There are two methods for triggering a configuration reload: 1. Symlink RUNTIME_ROOT to a different directory. 2. Update the contents inside `RUNTIME_ROOT/RUNTIME_SUBDIRECTORY/config/` directly. -The former is the default behavior. To use the latter method, set the `RUNTIME_WATCH_ROOT` environment variable to `false`. +The former is the default behavior. To use the latter method, set the `RUNTIME_WATCH_ROOT` environment variable to `false`. For more information on how runtime works you can read its [README](https://github.com/lyft/goruntime). @@ -377,7 +395,7 @@ The ratelimit service listens to HTTP 1.1 (by default on port 8080) with two end ## /json endpoint -Takes an HTTP POST with a JSON body of the form e.g. +Takes an HTTP POST with a JSON body of the form e.g. ```json { "domain": "dummy", @@ -389,7 +407,7 @@ Takes an HTTP POST with a JSON body of the form e.g. ] } ``` -The service will return an http 200 if this request is allowed (if no ratelimits exceeded) or 429 if one or more +The service will return an http 200 if this request is allowed (if no ratelimits exceeded) or 429 if one or more ratelimits were exceeded. The response is a RateLimitResponse encoded with @@ -432,7 +450,7 @@ You can specify the debug port with the `DEBUG_PORT` environment variable. It de # Local Cache -Ratelimit optionally uses [freecache](https://github.com/coocood/freecache) as its local caching layer, which stores the over-the-limit cache keys, and thus avoids reading the +Ratelimit optionally uses [freecache](https://github.com/coocood/freecache) as its local caching layer, which stores the over-the-limit cache keys, and thus avoids reading the redis cache again for the already over-the-limit keys. The local cache size can be configured via `LocalCacheSizeInBytes` in the [settings](https://github.com/envoyproxy/ratelimit/blob/master/src/settings/settings.go). If `LocalCacheSizeInBytes` is 0, local cache is disabled. diff --git a/docker-compose-example.yml b/docker-compose-example.yml new file mode 100644 index 000000000..f96879ee4 --- /dev/null +++ b/docker-compose-example.yml @@ -0,0 +1,87 @@ +version: "3" +services: + redis: + image: redis:alpine + expose: + - 6379 + ports: + - 6379:6379 + networks: + - ratelimit-network + + statsd: + image: prom/statsd-exporter:v0.18.0 + expose: + - 9125 + ports: + - 9125:9125 + networks: + - ratelimit-network + + ratelimit: + image: envoyproxy/ratelimit:master + command: /bin/ratelimit + ports: + - 8080:8080 + - 8081:8081 + - 6070:6070 + depends_on: + - redis + - statsd + networks: + - ratelimit-network + volumes: + - ./examples/ratelimit/config:/data/ratelimit/config + environment: + - USE_STATSD=true + - STATSD_HOST=statsd + - STATSD_PORT=9125 + - LOG_LEVEL=debug + - REDIS_SOCKET_TYPE=tcp + - REDIS_URL=redis:6379 + - RUNTIME_ROOT=/data + - RUNTIME_SUBDIRECTORY=ratelimit + - RUNTIME_WATCH_ROOT=false + + envoy-proxy: + image: envoyproxy/envoy-dev:latest + entrypoint: "/usr/local/bin/envoy" + command: + - "--service-node proxy" + - "--service-cluster proxy" + - "--config-path /etc/envoy/envoy.yaml" + - "--concurrency 1" + - "--mode serve" + - "--log-level info" + volumes: + - ./examples/envoy/proxy.yaml:/etc/envoy/envoy.yaml + networks: + - ratelimit-network + expose: + - "8888" + - "8001" + ports: + - "8888:8888" + - "8001:8001" + + envoy-mock: + image: envoyproxy/envoy-dev:latest + entrypoint: "/usr/local/bin/envoy" + command: + - "--service-node mock" + - "--service-cluster mock" + - "--config-path /etc/envoy/envoy.yaml" + - "--concurrency 1" + - "--mode serve" + - "--log-level info" + volumes: + - ./examples/envoy/mock.yaml:/etc/envoy/envoy.yaml + networks: + - ratelimit-network + expose: + - "9999" + ports: + - "9999:9999" + +networks: + ratelimit-network: diff --git a/docker-compose.yml b/docker-compose.yml index 51360d361..ac1ab9063 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -29,7 +29,8 @@ services: ratelimit: image: alpine:3.6 - command: /usr/local/bin/ratelimit + command: > + sh -c "until test -f /usr/local/bin/ratelimit; do sleep 5; done; /usr/local/bin/ratelimit" ports: - 8080:8080 - 8081:8081 diff --git a/examples/envoy/mock.yaml b/examples/envoy/mock.yaml new file mode 100644 index 000000000..bd85fc3d2 --- /dev/null +++ b/examples/envoy/mock.yaml @@ -0,0 +1,34 @@ +static_resources: + listeners: + - address: + socket_address: + address: 0.0.0.0 + port_value: 9999 + filter_chains: + - filters: + - name: envoy.http_connection_manager + config: + codec_type: auto + stat_prefix: ingress + route_config: + name: ingress + virtual_hosts: + - name: backend + domains: + - "*" + routes: + - match: + prefix: "/" + direct_response: + status: "200" + body: + inline_string: "Hello World" + http_filters: + - name: envoy.router + config: {} +admin: + access_log_path: "/dev/null" + address: + socket_address: + address: 0.0.0.0 + port_value: 8001 diff --git a/examples/envoy/proxy.yaml b/examples/envoy/proxy.yaml new file mode 100644 index 000000000..bb45503f9 --- /dev/null +++ b/examples/envoy/proxy.yaml @@ -0,0 +1,104 @@ +admin: + access_log_path: "/dev/null" + address: + socket_address: + address: 0.0.0.0 + port_value: 8001 +static_resources: + clusters: + - name: ratelimit + type: STRICT_DNS + connect_timeout: 1s + lb_policy: ROUND_ROBIN + protocol_selection: USE_CONFIGURED_PROTOCOL + http2_protocol_options: {} + load_assignment: + cluster_name: ratelimit + endpoints: + - lb_endpoints: + - endpoint: + address: + socket_address: + address: ratelimit + port_value: 8081 + - name: mock + connect_timeout: 1s + type: STRICT_DNS + lb_policy: ROUND_ROBIN + load_assignment: + cluster_name: mock + endpoints: + - lb_endpoints: + - endpoint: + address: + socket_address: + address: envoy-mock + port_value: 9999 + listeners: + - address: + socket_address: + address: 0.0.0.0 + port_value: 8888 + filter_chains: + - filters: + - name: envoy.http_connection_manager + config: + codec_type: auto + stat_prefix: ingress + http_filters: + - name: envoy.rate_limit + config: + domain: rl + request_type: external + stage: 0 + rate_limited_as_resource_exhausted: true + failure_mode_deny: false + rate_limit_service: + grpc_service: + envoy_grpc: + cluster_name: ratelimit + - name: envoy.router + config: {} + route_config: + name: route + virtual_hosts: + - name: backend + domains: + - "*" + routes: + - match: + prefix: /test + route: + cluster: mock + rate_limits: + - actions: + - source_cluster: {} + - destination_cluster: {} + - match: + prefix: /header + route: + cluster: mock + rate_limits: + - actions: + - request_headers: + header_name: "foo" + descriptor_key: "foo" + - match: + prefix: /twoheader + route: + cluster: mock + rate_limits: + - actions: + - request_headers: + header_name: "foo" + descriptor_key: "foo" + - request_headers: + header_name: "bar" + descriptor_key: "bar" + - actions: + - request_headers: + header_name: "foo" + descriptor_key: "foo" + - request_headers: + header_name: "baz" + descriptor_key: "baz" diff --git a/examples/ratelimit/config/example.yaml b/examples/ratelimit/config/example.yaml new file mode 100644 index 000000000..03e2f7839 --- /dev/null +++ b/examples/ratelimit/config/example.yaml @@ -0,0 +1,29 @@ +--- +domain: rl +descriptors: + - key: source_cluster + value: proxy + descriptors: + - key: destination_cluster + value: mock + rate_limit: + unit: minute + requests_per_unit: 1 + - key: foo + rate_limit: + unit: minute + requests_per_unit: 2 + descriptors: + - key: bar + rate_limit: + unit: minute + requests_per_unit: 3 + - key: bar + value: banned + rate_limit: + unit: minute + requests_per_unit: 0 + - key: baz + rate_limit: + unit: second + requests_per_unit: 1