Repository: envoyproxy/gateway
Pull request #939 (2023-01-26 00:51:28)

Translate RateLimitInfra into K8s resources
Relates to https://github.com/envoyproxy/gateway/issues/670

Signed-off-by: Arko Dasgupta <arko@tetrate.io>

codecov-commenter: # [Codecov](https://codecov.io/gh/envoyproxy/gateway/pull/939?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None) Report
> Merging [#939](https://codecov.io/gh/envoyproxy/gateway/pull/939?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None) (86bbaf7) into [main](https://codecov.io/gh/envoyproxy/gateway/commit/84044dae7b4869991c3beb3b07c114ffba274227?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None) (84044da) will **decrease** coverage by `2.34%`.
> The diff coverage is `21.11%`.

:mega: This organization is not using Codecov’s [GitHub App Integration](https://github.com/apps/codecov). We recommend you install it so Codecov can continue to function properly for your repositories. [Learn more](https://about.codecov.io/blog/codecov-is-updating-its-github-integration/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None)

```diff
@@            Coverage Diff             @@
##             main     #939      +/-   ##
==========================================
- Coverage   63.84%   61.51%   -2.34%     
==========================================
  Files          54       54              
  Lines        7834     8140     +306     
==========================================
+ Hits         5002     5007       +5     
- Misses       2519     2818     +299     
- Partials      313      315       +2     
```


| [Impacted Files](https://codecov.io/gh/envoyproxy/gateway/pull/939?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None) | Coverage Δ | |
|---|---|---|
| [internal/cmd/server.go](https://codecov.io/gh/envoyproxy/gateway/pull/939?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None#diff-aW50ZXJuYWwvY21kL3NlcnZlci5nbw==) | `6.97% <0.00%> (-0.23%)` | :arrow_down: |
| [internal/infrastructure/kubernetes/infra.go](https://codecov.io/gh/envoyproxy/gateway/pull/939?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None#diff-aW50ZXJuYWwvaW5mcmFzdHJ1Y3R1cmUva3ViZXJuZXRlcy9pbmZyYS5nbw==) | `27.63% <7.50%> (-16.12%)` | :arrow_down: |
| [internal/infrastructure/kubernetes/service.go](https://codecov.io/gh/envoyproxy/gateway/pull/939?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None#diff-aW50ZXJuYWwvaW5mcmFzdHJ1Y3R1cmUva3ViZXJuZXRlcy9zZXJ2aWNlLmdv) | `40.54% <14.28%> (-31.96%)` | :arrow_down: |
| [internal/infrastructure/kubernetes/deployment.go](https://codecov.io/gh/envoyproxy/gateway/pull/939?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None#diff-aW50ZXJuYWwvaW5mcmFzdHJ1Y3R1cmUva3ViZXJuZXRlcy9kZXBsb3ltZW50Lmdv) | `54.02% <16.45%> (-32.71%)` | :arrow_down: |
| [internal/infrastructure/kubernetes/labels.go](https://codecov.io/gh/envoyproxy/gateway/pull/939?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None#diff-aW50ZXJuYWwvaW5mcmFzdHJ1Y3R1cmUva3ViZXJuZXRlcy9sYWJlbHMuZ28=) | `77.77% <33.33%> (-22.23%)` | :arrow_down: |
| [internal/infrastructure/kubernetes/configmap.go](https://codecov.io/gh/envoyproxy/gateway/pull/939?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None#diff-aW50ZXJuYWwvaW5mcmFzdHJ1Y3R1cmUva3ViZXJuZXRlcy9jb25maWdtYXAuZ28=) | `52.17% <40.00%> (-23.69%)` | :arrow_down: |
| [...ternal/infrastructure/kubernetes/serviceaccount.go](https://codecov.io/gh/envoyproxy/gateway/pull/939?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None#diff-aW50ZXJuYWwvaW5mcmFzdHJ1Y3R1cmUva3ViZXJuZXRlcy9zZXJ2aWNlYWNjb3VudC5nbw==) | `56.66% <42.22%> (-21.30%)` | :arrow_down: |
| [internal/provider/kubernetes/helpers.go](https://codecov.io/gh/envoyproxy/gateway/pull/939?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None#diff-aW50ZXJuYWwvcHJvdmlkZXIva3ViZXJuZXRlcy9oZWxwZXJzLmdv) | `79.72% <0.00%> (-3.38%)` | :arrow_down: |
| [internal/provider/kubernetes/controller.go](https://codecov.io/gh/envoyproxy/gateway/pull/939?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None#diff-aW50ZXJuYWwvcHJvdmlkZXIva3ViZXJuZXRlcy9jb250cm9sbGVyLmdv) | `48.41% <0.00%> (-0.68%)` | :arrow_down: |
| ... and [1 more](https://codecov.io/gh/envoyproxy/gateway/pull/939?src=pr&el=tree-more&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None) | |

Help us with your feedback. Take ten seconds to tell us [how you rate us](https://about.codecov.io/nps?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None). Have a feature suggestion? [Share it here.](https://app.codecov.io/gh/feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None)


Patch:
diff --git a/docs/v0.2.0/user/http-redirect.md b/docs/v0.2.0/user/http-redirect.md
index c11b346b5..5b6588535 100644
--- a/docs/v0.2.0/user/http-redirect.md
+++ b/docs/v0.2.0/user/http-redirect.md
@@ -5,10 +5,13 @@ HTTPRoute rules cannot use both filter types at once. Currently, Envoy Gateway o
 [HTTPRoute filters][] which consist of `RequestRedirect` and `RequestHeaderModifier` at the time of this writing. To
 learn more about HTTP routing, refer to the [Gateway API documentation][].
 
-Follow the steps from the [Secure Gateways](secure-gateways.md) to install Envoy Gateway and the example manifest. Do
-not proceed until you can curl the example backend from the Quickstart guide using HTTPS.
+## Prerequisites
+
+Follow the steps from the [Secure Gateways](secure-gateways.md) to install Envoy Gateway and the example manifest.
+Before proceeding, you should be able to query the example backend using HTTPS.
 
 ## Redirects
+
 Redirects return HTTP 3XX responses to a client, instructing it to retrieve a different resource. A
 [`RequestRedirect` filter][req_filter] instructs Gateways to emit a redirect response to requests that match the rule.
 For example, to issue a permanent redirect (301) from HTTP to HTTPS, configure `requestRedirect.statusCode=301` and
@@ -68,6 +71,7 @@ If you followed the steps in the [Secure Gateways](secure-gateways.md) guide, yo
 location.
 
 ## Path Redirects
+
 Path redirects use an HTTP Path Modifier to replace either entire paths or path prefixes. For example, the HTTPRoute
 below will issue a 302 redirect to all `path.redirect.example` requests whose path begins with `/get` to `/status/200`.
 
diff --git a/docs/v0.2.0/user/http-request-headers.md b/docs/v0.2.0/user/http-request-headers.md
index 98b750db3..b51bf946c 100644
--- a/docs/v0.2.0/user/http-request-headers.md
+++ b/docs/v0.2.0/user/http-request-headers.md
@@ -1,16 +1,26 @@
 # HTTP Request Headers
 
-The [HTTPRoute][] resource can modify the headers of a request before forwarding it to the upstream service. HTTPRoute rules cannot use both filter types at once. Currently, Envoy Gateway only supports __core__
-[HTTPRoute filters][] which consist of `RequestRedirect` and `RequestHeaderModifier` at the time of this writing. To
-learn more about HTTP routing, refer to the [Gateway API documentation][].
+The [HTTPRoute][] resource can modify the headers of a request before forwarding it to the upstream service. HTTPRoute
+rules cannot use both filter types at once. Currently, Envoy Gateway only supports __core__ [HTTPRoute filters][] which
+consist of `RequestRedirect` and `RequestHeaderModifier` at the time of this writing. To learn more about HTTP routing,
+refer to the [Gateway API documentation][].
 
-A [`RequestHeaderModifier` filter][req_filter] instructs Gateways to modify the headers in requests that match the rule before forwarding the request upstream. Note that the `RequestHeaderModifier` filter will only modify headers before the request is sent from Envoy to the upstream service and will not affect response headers returned to the downstream client.
+A [`RequestHeaderModifier` filter][req_filter] instructs Gateways to modify the headers in requests that match the rule
+before forwarding the request upstream. Note that the `RequestHeaderModifier` filter will only modify headers before the
+request is sent from Envoy to the upstream service and will not affect response headers returned to the downstream
+client.
 
-Follow the steps from the [Quickstart Guide](QUICKSTART.md) to install Envoy Gateway and then install the example resources used for this guide.
+## Prerequisites
+
+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.
 
 ## Adding Request Headers
 
-The `RequestHeaderModifier` filter can add new headers to a request before it is sent to the upstream. If the request does not have the header configured by the filter, then that header will be added to the request. If the request already has the header configured by the filter, then the value of the header in the filter will be appended to the value of the header in the request.
+The `RequestHeaderModifier` filter can add new headers to a request before it is sent to the upstream. If the request
+does not have the header configured by the filter, then that header will be added to the request. If the request already
+has the header configured by the filter, then the value of the header in the filter will be appended to the value of the
+header in the request.
 
 ```shell
 cat <<EOF | kubectl apply -f -
@@ -55,8 +65,9 @@ Get the Gateway's address:
 export GATEWAY_HOST=$(kubectl get gateway/eg -o jsonpath='{.status.addresses[0].value}')
 ```
 
-Querying `headers.example/get` should result in a `200` response from the example Gateway and the output from the example
-app should indicate that the upstream example app received the header `add-header` with the value: `something,foo`
+Querying `headers.example/get` should result in a `200` response from the example Gateway and the output from the
+example app should indicate that the upstream example app received the header `add-header` with the value:
+`something,foo`
 
 ```console
 $ curl -vvv --header "Host: headers.example" "http://${GATEWAY_HOST}:8080/get" --header "add-header: something"
@@ -89,7 +100,10 @@ $ curl -vvv --header "Host: headers.example" "http://${GATEWAY_HOST}:8080/get" -
 
 ## Setting Request Headers
 
-Setting headers is similar to adding headers. If the request does not have the header configured by the filter, then it will be added, but unlike [adding request headers](#adding-request-headers) which will append the value of the header if the request already contains it, setting a header will cause the value to be replaced by the value configured in the filter.
+Setting headers is similar to adding headers. If the request does not have the header configured by the filter, then it
+will be added, but unlike [adding request headers](#adding-request-headers) which will append the value of the header if
+the request already contains it, setting a header will cause the value to be replaced by the value configured in the
+filter.
 
 ```shell
 cat <<EOF | kubectl apply -f -
@@ -122,8 +136,9 @@ spec:
 EOF
 ```
 
-Querying `headers.example/get` should result in a `200` response from the example Gateway and the output from the example
-app should indicate that the upstream example app received the header `add-header` with the original value `something` replaced by `foo`.
+Querying `headers.example/get` should result in a `200` response from the example Gateway and the output from the
+example app should indicate that the upstream example app received the header `add-header` with the original value
+`something` replaced by `foo`.
 
 ```console
 $ curl -vvv --header "Host: headers.example" "http://${GATEWAY_HOST}:8080/get" --header "set-header: something"
@@ -156,7 +171,10 @@ $ curl -vvv --header "Host: headers.example" "http://${GATEWAY_HOST}:8080/get" -
 
 Headers can be removed from a request by simply supplying a list of header names.
 
-Setting headers is similar to adding headers. If the request does not have the header configured by the filter, then it will be added, but unlike [adding request headers](#adding-request-headers) which will append the value of the header if the request already contains it, setting a header will cause the value to be replaced by the value configured in the filter.
+Setting headers is similar to adding headers. If the request does not have the header configured by the filter, then it
+will be added, but unlike [adding request headers](#adding-request-headers) which will append the value of the header if
+the request already contains it, setting a header will cause the value to be replaced by the value configured in the
+filter.
 
 ```shell
 cat <<EOF | kubectl apply -f -
@@ -187,9 +205,9 @@ spec:
 EOF
 ```
 
-Querying `headers.example/get` should result in a `200` response from the example Gateway and the output from the example
-app should indicate that the upstream example app received the header `add-header`, but the header `remove-header` that was sent by curl
-was removed before the upstream received the request.
+Querying `headers.example/get` should result in a `200` response from the example Gateway and the output from the
+example app should indicate that the upstream example app received the header `add-header`, but the header
+`remove-header` that was sent by curl was removed before the upstream received the request.
 
 ```console
 $ curl -vvv --header "Host: headers.example" "http://${GATEWAY_HOST}:8080/get" --header "add-header: something" --header "remove-header: foo"
@@ -221,49 +239,7 @@ $ curl -vvv --header "Host: headers.example" "http://${GATEWAY_HOST}:8080/get" -
 
 ## Combining Filters
 
-Headers can be added/set/removed in separate filters on the same HTTPRoute and they will all perform as expected
-
-```shell
-cat <<EOF | kubectl apply -f -
-apiVersion: gateway.networking.k8s.io/v1beta1
-kind: HTTPRoute
-metadata:
-  name: http-headers
-spec:
-  parentRefs:
-  - name: eg
-  hostnames:
-  - headers.example
-  rules:
-  - matches:
-    - path:
-        type: PathPrefix
-        value: /
-    backendRefs:
-    - group: ""
-      kind: Service
-      name: backend
-      port: 3000
-      weight: 1
-    filters:
-    - type: RequestHeaderModifier
-      requestHeaderModifier:
-        add:
-        - name: "added-header"
-          value: "foo"
-    - type: RequestHeaderModifier
-      requestHeaderModifier:
-        set:
-        - name: "set-header"
-          value: "bar"
-    - type: RequestHeaderModifier
-      requestHeaderModifier:
-        remove:
-        - "removed-header"
-EOF
-```
-
-Alternatively, it is easier to combine all of the header operations into a single filter.
+Headers can be added/set/removed in a single filter on the same HTTPRoute and they will all perform as expected
 
 ```shell
 cat <<EOF | kubectl apply -f -
diff --git a/docs/v0.2.0/user/http-routing.md b/docs/v0.2.0/user/http-routing.md
index cc7d445ca..3e9af9fe4 100644
--- a/docs/v0.2.0/user/http-routing.md
+++ b/docs/v0.2.0/user/http-routing.md
@@ -5,19 +5,36 @@ Kubernetes backends. Currently, the only supported backend supported by Envoy Ga
 shows how to route traffic based on host, header, and path fields and forward the traffic to different Kubernetes
 Services. To learn more about HTTP routing, refer to the [Gateway API documentation][].
 
-Follow the steps from the [Quickstart Guide](quickstart.md) to install Envoy Gateway and then install the example
-resources used for this guide.
+## Prerequisites
+
+Install Envoy Gateway:
+
+```shell
+kubectl apply -f https://github.com/envoyproxy/gateway/releases/download/v0.2.0/install.yaml
+```
+
+Wait for Envoy Gateway to become available:
+
+```shell
+kubectl wait --timeout=5m -n envoy-gateway-system deployment/envoy-gateway --for=condition=Available
+```
+
+## Installation
+
+Install the HTTP routing example resources:
 
 ```shell
 kubectl apply -f https://raw.githubusercontent.com/envoyproxy/gateway/v0.2.0/examples/kubernetes/http-routing.yaml
 ```
 
 The manifest installs a [GatewayClass][], [Gateway][], four Deployments, four Services, and three HTTPRoute resources.
+The GatewayClass is a cluster-scoped resource that represents a class of Gateways that can be instantiated.
 
-The GatewayClass is a cluster-scoped resource that represents a class of Gateways that can be instantiated. Envoy
-Gateway is configured by default to manage GatewayClasses with
+__Note:__ Envoy Gateway is configured by default to manage a GatewayClass with
 `controllerName: gateway.envoyproxy.io/gatewayclass-controller`.
 
+## Verification
+
 Check the status of the GatewayClass:
 
 ```shell
@@ -51,8 +68,11 @@ kubectl get httproutes --selector=example=http-routing -o yaml
 ```
 
 The status for each HTTPRoute should surface "Accepted=True" and a `parentRef` that references the example Gateway.
-The `example-route` matches any traffic for "example.com" and forwards it to the "example-svc" Service. Before testing
-HTTP routing to the `example-svc` backend, get the Gateway's address.
+The `example-route` matches any traffic for "example.com" and forwards it to the "example-svc" Service.
+
+## Testing the Configuration
+
+Before testing HTTP routing to the `example-svc` backend, get the Gateway's address.
 
 ```shell
 export GATEWAY_HOST=$(kubectl get gateway/example-gateway -o jsonpath='{.status.addresses[0].value}')
diff --git a/docs/v0.2.0/user/http-traffic-splitting.md b/docs/v0.2.0/user/http-traffic-splitting.md
index 562bbcc0f..a3f8fe60b 100644
--- a/docs/v0.2.0/user/http-traffic-splitting.md
+++ b/docs/v0.2.0/user/http-traffic-splitting.md
@@ -1,8 +1,13 @@
 # HTTPRoute Traffic Splitting
 
-The [HTTPRoute][] resource allows one or more [backendRefs][] to be provided. Requests will be routed to these upstreams if they match the rules of the HTTPRoute. If an invalid backendRef is configured, then HTTP responses will be returned with status code `500` for all of the requests that would have been sent to that backend.
+The [HTTPRoute][] resource allows one or more [backendRefs][] to be provided. Requests will be routed to these upstreams
+if they match the rules of the HTTPRoute. If an invalid backendRef is configured, then HTTP responses will be returned
+with status code `500` for all requests that would have been sent to that backend.
 
-Follow the steps from the [Quickstart Guide](QUICKSTART.md) to install Envoy Gateway and then install the example resources used for this guide.
+## Installation
+
+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.
 
 ## Single backendRef
 
@@ -44,8 +49,9 @@ Get the Gateway's address:
 export GATEWAY_HOST=$(kubectl get gateway/eg -o jsonpath='{.status.addresses[0].value}')
 ```
 
-Querying `backends.example/get` should result in a `200` response from the example Gateway and the output from the example
-app should indicate which pod handled the request. There is only one pod in the deployment for the example app from the quickstart, so it will be the same on all subsequent requests.
+Querying `backends.example/get` should result in a `200` response from the example Gateway and the output from the
+example app should indicate which pod handled the request. There is only one pod in the deployment for the example app
+from the quickstart, so it will be the same on all subsequent requests.
 
 ```console
 $ curl -vvv --header "Host: backends.example" "http://${GATEWAY_HOST}:8080/get"
@@ -74,7 +80,8 @@ $ curl -vvv --header "Host: backends.example" "http://${GATEWAY_HOST}:8080/get"
 
 ## Multiple backendRefs
 
-If multiple backendRefs are configured, then traffic will be split between the backendRefs equally unless a weight is configured.
+If multiple backendRefs are configured, then traffic will be split between the backendRefs equally unless a weight is
+configured.
 
 First, create a second instance of the example app from the quickstart:
 
@@ -166,8 +173,9 @@ spec:
 EOF
 ```
 
-Querying `backends.example/get` should result in `200` responses from the example Gateway and the output from the example app
-that indicates which pod handled the request should switch between the first pod and the second one from the new deployment on subsequent requests.
+Querying `backends.example/get` should result in `200` responses from the example Gateway and the output from the
+example app that indicates which pod handled the request should switch between the first pod and the second one from the
+new deployment on subsequent requests.
 
 ```console
 $ curl -vvv --header "Host: backends.example" "http://${GATEWAY_HOST}:8080/get"
@@ -196,11 +204,16 @@ $ curl -vvv --header "Host: backends.example" "http://${GATEWAY_HOST}:8080/get"
 
 ## Weighted backendRefs
 
-If multiple backendRefs are configured and an un-even traffic split between the backends is desired, then the `weight` field can be used to control the weight of requests to each backend. If weight is not configured for a backendRef it is assumed to be `1`.
+If multiple backendRefs are configured and an un-even traffic split between the backends is desired, then the `weight`
+field can be used to control the weight of requests to each backend. If weight is not configured for a backendRef it is
+assumed to be `1`.
 
-The [weight field in a backendRef][backendRefs] controls the distribution of the traffic split. The proportion of requests to a single backendRef is calculated by dividing its `weight` by the sum of all backendRef weights in the HTTPRoute. The weight is not a percentage and the sum of all weights does not need to add up to 100.
+The [weight field in a backendRef][backendRefs] controls the distribution of the traffic split. The proportion of
+requests to a single backendRef is calculated by dividing its `weight` by the sum of all backendRef weights in the
+HTTPRoute. The weight is not a percentage and the sum of all weights does not need to add up to 100.
 
-The HTTPRoute below will configure the gateway to send 80% of the traffic to the backend service, and 20% to the backend-2 service.
+The HTTPRoute below will configure the gateway to send 80% of the traffic to the backend service, and 20% to the
+backend-2 service.
 
 ```shell
 cat <<EOF | kubectl apply -f -
@@ -236,13 +249,17 @@ EOF
 
 backendRefs can be considered invalid for the following reasons:
 
-- The `group` field is configured to something other than `""`. Currently, only the core API group (specified by omitting the group field or setting it to an empty string) is supported
-- The `kind` field is configured to anything other than `Service`. Envoy Gateway currently only supports Kubernetes Service backendRefs
+- The `group` field is configured to something other than `""`. Currently, only the core API group (specified by
+  omitting the group field or setting it to an empty string) is supported
+- The `kind` field is configured to anything other than `Service`. Envoy Gateway currently only supports Kubernetes
+  Service backendRefs
 - The backendRef configures a service with a `namespace` not permitted by any existing ReferenceGrants
 - The `port` field is not configured or is configured to a port that does not exist on the Service
 - The named Service configured by the backendRef cannot be found
 
-Modifying the above example to make the backend-2 backendRef invalid by using a port that does not exist on the Service will result in 80% of the traffic being sent to the backend service, and 20% of the traffic receiving a HTTP response with status code `500`.
+Modifying the above example to make the backend-2 backendRef invalid by using a port that does not exist on the Service
+will result in 80% of the traffic being sent to the backend service, and 20% of the traffic receiving an HTTP response
+with status code `500`.
 
 ```shell
 cat <<EOF | kubectl apply -f -
diff --git a/docs/v0.2.0/user/quickstart.md b/docs/v0.2.0/user/quickstart.md
index 02da2b769..5361ca187 100644
--- a/docs/v0.2.0/user/quickstart.md
+++ b/docs/v0.2.0/user/quickstart.md
@@ -6,7 +6,7 @@ This guide will help you get started with Envoy Gateway in a few simple steps.
 
 A Kubernetes cluster.
 
-__Note:__ Envoy Gateway is tested against Kubernetes v1.24.0.
+__Note:__ Refer to the [Compatibility Matrix](../intro/compatibility.rst) for supported Kubernetes versions.
 
 ## Installation
 
diff --git a/docs/v0.2.0/user/secure-gateways.md b/docs/v0.2.0/user/secure-gateways.md
index 8305b0165..cd5199495 100644
--- a/docs/v0.2.0/user/secure-gateways.md
+++ b/docs/v0.2.0/user/secure-gateways.md
@@ -5,11 +5,8 @@ testing and demonstration purposes only.
 
 ## Prerequisites
 
-- A Kubernetes cluster with `kubectl` context configured for the cluster.
 - OpenSSL to generate TLS assets.
 
-__Note:__ Envoy Gateway is tested against Kubernetes v1.24.
-
 ## Installation
 
 Follow the steps from the [Quickstart Guide](quickstart.md) to install Envoy Gateway and the example manifest.
diff --git a/docs/v0.2.0/user/tls-passthrough.md b/docs/v0.2.0/user/tls-passthrough.md
index 444c43e77..f089350d3 100644
--- a/docs/v0.2.0/user/tls-passthrough.md
+++ b/docs/v0.2.0/user/tls-passthrough.md
@@ -1,18 +1,17 @@
 # TLS Passthrough
 
-This guide will walk through the steps required to configure TLS Passthrough via Envoy Gateway. Unlike configuring Secure Gateways, where the Gateway terminates the client TLS connection, TLS Passthrough allows the application itself to terminate the TLS connection, while the Gateway routes the requests to the application based on SNI headers.
+This guide will walk through the steps required to configure TLS Passthrough via Envoy Gateway. Unlike configuring
+Secure Gateways, where the Gateway terminates the client TLS connection, TLS Passthrough allows the application itself
+to terminate the TLS connection, while the Gateway routes the requests to the application based on SNI headers.
 
 ## Prerequisites
 
-- A Kubernetes cluster with `kubectl` context configured for the cluster.
 - OpenSSL to generate TLS assets.
 
-__Note:__ Envoy Gateway is tested against Kubernetes v1.24.0.
-
 ## Installation
 
 Follow the steps from the [Quickstart Guide](quickstart.md) to install Envoy Gateway and the example manifest.
-Before proceeding, you should be able to curl the example backend using HTTP.
+Before proceeding, you should be able to query the example backend using HTTP.
 
 ## TLS Certificates
 
@@ -48,7 +47,8 @@ Deploy TLS Passthrough application Deployment, Service and TLSRoute:
 kubectl apply -f https://raw.githubusercontent.com/envoyproxy/gateway/v0.2.0/examples/kubernetes/tls-passthrough.yaml
 ```
 
-Patch the Gateway from the Quickstart guide to include a TLS listener that listens on port `6443` and is configured for TLS mode Passthrough:
+Patch the Gateway from the Quickstart guide to include a TLS listener that listens on port `6443` and is configured for
+TLS mode Passthrough:
 
 ```shell
 kubectl patch gateway eg --type=json --patch '[{
diff --git a/go.mod b/go.mod
index c53964f06..945c32eb5 100644
--- a/go.mod
+++ b/go.mod
@@ -16,14 +16,14 @@ require (
 	github.com/tsaarni/certyaml v0.9.1
 	go.uber.org/zap v1.24.0
 	golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e
-	google.golang.org/grpc v1.52.0
+	google.golang.org/grpc v1.52.3
 	google.golang.org/protobuf v1.28.1
 	gopkg.in/yaml.v3 v3.0.1
 	k8s.io/api v0.26.1
 	k8s.io/apimachinery v0.26.1
 	k8s.io/client-go v0.26.1
 	k8s.io/utils v0.0.0-20221128185143-99ec85e7a448
-	sigs.k8s.io/controller-runtime v0.14.1
+	sigs.k8s.io/controller-runtime v0.14.2
 	sigs.k8s.io/gateway-api v0.6.0
 	sigs.k8s.io/yaml v1.3.0
 )
@@ -82,8 +82,8 @@ require (
 	google.golang.org/genproto v0.0.0-20221118155620-16455021b5e6 // indirect
 	gopkg.in/inf.v0 v0.9.1 // indirect
 	gopkg.in/yaml.v2 v2.4.0 // indirect
-	k8s.io/apiextensions-apiserver v0.26.0 // indirect
-	k8s.io/component-base v0.26.0 // indirect
+	k8s.io/apiextensions-apiserver v0.26.1 // indirect
+	k8s.io/component-base v0.26.1 // indirect
 	k8s.io/klog/v2 v2.80.1 // indirect
 	k8s.io/kube-openapi v0.0.0-20221012153701-172d655c2280 // indirect
 	sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2 // indirect
diff --git a/go.sum b/go.sum
index 308c53dcc..341363197 100644
--- a/go.sum
+++ b/go.sum
@@ -652,8 +652,8 @@ google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAG
 google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34=
 google.golang.org/grpc v1.42.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU=
 google.golang.org/grpc v1.45.0/go.mod h1:lN7owxKUQEqMfSyQikvvk5tf/6zMPsrK+ONuO11+0rQ=
-google.golang.org/grpc v1.52.0 h1:kd48UiU7EHsV4rnLyOJRuP/Il/UHE7gdDAQ+SZI7nZk=
-google.golang.org/grpc v1.52.0/go.mod h1:pu6fVzoFb+NBYNAvQL08ic+lvB2IojljRYuun5vorUY=
+google.golang.org/grpc v1.52.3 h1:pf7sOysg4LdgBqduXveGKrcEwbStiK2rtfghdzlUYDQ=
+google.golang.org/grpc v1.52.3/go.mod h1:pu6fVzoFb+NBYNAvQL08ic+lvB2IojljRYuun5vorUY=
 google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
 google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
 google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
@@ -702,14 +702,14 @@ honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9
 honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
 k8s.io/api v0.26.1 h1:f+SWYiPd/GsiWwVRz+NbFyCgvv75Pk9NK6dlkZgpCRQ=
 k8s.io/api v0.26.1/go.mod h1:xd/GBNgR0f707+ATNyPmQ1oyKSgndzXij81FzWGsejg=
-k8s.io/apiextensions-apiserver v0.26.0 h1:Gy93Xo1eg2ZIkNX/8vy5xviVSxwQulsnUdQ00nEdpDo=
-k8s.io/apiextensions-apiserver v0.26.0/go.mod h1:7ez0LTiyW5nq3vADtK6C3kMESxadD51Bh6uz3JOlqWQ=
+k8s.io/apiextensions-apiserver v0.26.1 h1:cB8h1SRk6e/+i3NOrQgSFij1B2S0Y0wDoNl66bn8RMI=
+k8s.io/apiextensions-apiserver v0.26.1/go.mod h1:AptjOSXDGuE0JICx/Em15PaoO7buLwTs0dGleIHixSM=
 k8s.io/apimachinery v0.26.1 h1:8EZ/eGJL+hY/MYCNwhmDzVqq2lPl3N3Bo8rvweJwXUQ=
 k8s.io/apimachinery v0.26.1/go.mod h1:tnPmbONNJ7ByJNz9+n9kMjNP8ON+1qoAIIC70lztu74=
 k8s.io/client-go v0.26.1 h1:87CXzYJnAMGaa/IDDfRdhTzxk/wzGZ+/HUQpqgVSZXU=
 k8s.io/client-go v0.26.1/go.mod h1:IWNSglg+rQ3OcvDkhY6+QLeasV4OYHDjdqeWkDQZwGE=
-k8s.io/component-base v0.26.0 h1:0IkChOCohtDHttmKuz+EP3j3+qKmV55rM9gIFTXA7Vs=
-k8s.io/component-base v0.26.0/go.mod h1:lqHwlfV1/haa14F/Z5Zizk5QmzaVf23nQzCwVOQpfC8=
+k8s.io/component-base v0.26.1 h1:4ahudpeQXHZL5kko+iDHqLj/FSGAEUnSVO0EBbgDd+4=
+k8s.io/component-base v0.26.1/go.mod h1:VHrLR0b58oC035w6YQiBSbtsf0ThuSwXP+p5dD/kAWU=
 k8s.io/klog/v2 v2.80.1 h1:atnLQ121W371wYYFawwYx1aEY2eUfs4l3J72wtgAwV4=
 k8s.io/klog/v2 v2.80.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0=
 k8s.io/kube-openapi v0.0.0-20221012153701-172d655c2280 h1:+70TFaan3hfJzs+7VK2o+OGxg8HsuBr/5f6tVAjDu6E=
@@ -719,8 +719,8 @@ k8s.io/utils v0.0.0-20221128185143-99ec85e7a448/go.mod h1:OLgZIPagt7ERELqWJFomSt
 rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
 rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
 rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
-sigs.k8s.io/controller-runtime v0.14.1 h1:vThDes9pzg0Y+UbCPY3Wj34CGIYPgdmspPm2GIpxpzM=
-sigs.k8s.io/controller-runtime v0.14.1/go.mod h1:GaRkrY8a7UZF0kqFFbUKG7n9ICiTY5T55P1RiE3UZlU=
+sigs.k8s.io/controller-runtime v0.14.2 h1:P6IwDhbsRWsBClt/8/h8Zy36bCuGuW5Op7MHpFrN/60=
+sigs.k8s.io/controller-runtime v0.14.2/go.mod h1:WqIdsAY6JBsjfc/CqO0CORmNtoCtE4S6qbPc9s68h+0=
 sigs.k8s.io/gateway-api v0.6.0 h1:v2FqrN2ROWZLrSnI2o91taHR8Sj3s+Eh3QU7gLNWIqA=
 sigs.k8s.io/gateway-api v0.6.0/go.mod h1:EYJT+jlPWTeNskjV0JTki/03WX1cyAnBhwBJfYHpV/0=
 sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2 h1:iXTIw73aPyC+oRdyqqvVJuloN1p0AC/kzH07hu3NE+k=
diff --git a/internal/cmd/server.go b/internal/cmd/server.go
index 27d3c4472..602802600 100644
--- a/internal/cmd/server.go
+++ b/internal/cmd/server.go
@@ -155,16 +155,19 @@ func setupRunners(cfg *config.Server) error {
 		return err
 	}
 
-	// Start the Global RateLimit Runner
-	// It subscribes to the xds Resources and translates it to Envoy Ratelimit Service
-	// infrastructure and configuration.
-	rateLimitRunner := ratelimitrunner.New(&ratelimitrunner.Config{
-		Server:           *cfg,
-		XdsIR:            xdsIR,
-		RateLimitInfraIR: rateLimitInfraIR,
-	})
-	if err := rateLimitRunner.Start(ctx); err != nil {
-		return err
+	// Start the global rateLimit runner if it has been enabled through the config
+	if cfg.EnvoyGateway.RateLimit != nil {
+		// Start the Global RateLimit Runner
+		// It subscribes to the xds Resources and translates it to Envoy Ratelimit Service
+		// infrastructure and configuration.
+		rateLimitRunner := ratelimitrunner.New(&ratelimitrunner.Config{
+			Server:           *cfg,
+			XdsIR:            xdsIR,
+			RateLimitInfraIR: rateLimitInfraIR,
+		})
+		if err := rateLimitRunner.Start(ctx); err != nil {
+			return err
+		}
 	}
 
 	// Wait until done
diff --git a/internal/gatewayapi/filters.go b/internal/gatewayapi/filters.go
index 10b2f98c0..a09062a67 100644
--- a/internal/gatewayapi/filters.go
+++ b/internal/gatewayapi/filters.go
@@ -681,7 +681,7 @@ func (t *Translator) processExtensionRefHTTPFilter(filter v1beta1.HTTPRouteFilte
 									SafeRegex: header.Value,
 								}
 								rules[i].HeaderMatches = append(rules[i].HeaderMatches, m)
-							case *header.Type == egv1a1.HeaderMatchRegularExpression && header.Value == nil:
+							case *header.Type == egv1a1.HeaderMatchDistinct && header.Value == nil:
 								m := &ir.StringMatch{
 									Name:     header.Name,
 									Distinct: true,
diff --git a/internal/globalratelimit/runner/runner.go b/internal/globalratelimit/runner/runner.go
index 46ccd96e2..0f1ed3c99 100644
--- a/internal/globalratelimit/runner/runner.go
+++ b/internal/globalratelimit/runner/runner.go
@@ -70,11 +70,6 @@ func (r *Runner) subscribeAndTranslate(ctx context.Context) {
 func (r *Runner) translate(xdsIRs []*ir.Xds) (*ir.RateLimitInfra, error) {
 	rlInfra := new(ir.RateLimitInfra)
 
-	// Return empty IR if ratelimit has not been enabled yet in the EnvoyGateway API
-	if r.EnvoyGateway.RateLimit == nil {
-		return nil, nil
-	}
-
 	for _, xdsIR := range xdsIRs {
 		for _, listener := range xdsIR.HTTP {
 			config := translator.BuildRateLimitServiceConfig(listener)
diff --git a/internal/infrastructure/kubernetes/configmap.go b/internal/infrastructure/kubernetes/configmap.go
index 4e8dcfec9..b15019789 100644
--- a/internal/infrastructure/kubernetes/configmap.go
+++ b/internal/infrastructure/kubernetes/configmap.go
@@ -45,8 +45,8 @@ var (
 		`"private_key":{"filename":"%s"}}}]}`, xdsTLSCertFilename, xdsTLSKeyFilename)
 )
 
-// expectedConfigMap returns the expected ConfigMap based on the provided infra.
-func (i *Infra) expectedConfigMap(infra *ir.Infra) (*corev1.ConfigMap, error) {
+// expectedProxyConfigMap returns the expected ConfigMap based on the provided infra.
+func (i *Infra) expectedProxyConfigMap(infra *ir.Infra) (*corev1.ConfigMap, error) {
 	// Set the labels based on the owning gateway name.
 	labels := envoyLabels(infra.GetProxyInfra().GetProxyMetadata().Labels)
 	if len(labels[gatewayapi.OwningGatewayNamespaceLabel]) == 0 || len(labels[gatewayapi.OwningGatewayNameLabel]) == 0 {
@@ -56,7 +56,7 @@ func (i *Infra) expectedConfigMap(infra *ir.Infra) (*corev1.ConfigMap, error) {
 	return &corev1.ConfigMap{
 		ObjectMeta: metav1.ObjectMeta{
 			Namespace: i.Namespace,
-			Name:      expectedConfigMapName(infra.Proxy.Name),
+			Name:      expectedProxyConfigMapName(infra.Proxy.Name),
 			Labels:    labels,
 		},
 		Data: map[string]string{
@@ -66,48 +66,99 @@ func (i *Infra) expectedConfigMap(infra *ir.Infra) (*corev1.ConfigMap, error) {
 	}, nil
 }
 
-// createOrUpdateConfigMap creates a ConfigMap in the Kube api server based on the provided
+// createOrUpdateProxyConfigMap creates a ConfigMap in the Kube api server based on the provided
 // infra, if it doesn't exist and updates it if it does.
-func (i *Infra) createOrUpdateConfigMap(ctx context.Context, infra *ir.Infra) (*corev1.ConfigMap, error) {
-	cm, err := i.expectedConfigMap(infra)
+func (i *Infra) createOrUpdateProxyConfigMap(ctx context.Context, infra *ir.Infra) error {
+	cm, err := i.expectedProxyConfigMap(infra)
 	if err != nil {
-		return nil, err
+		return err
 	}
 
+	return i.createOrUpdateConfigMap(ctx, cm)
+}
+
+// deleteProxyConfigMap deletes the Envoy ConfigMap in the kube api server, if it exists.
+func (i *Infra) deleteProxyConfigMap(ctx context.Context, infra *ir.Infra) error {
+	cm := &corev1.ConfigMap{
+		ObjectMeta: metav1.ObjectMeta{
+			Namespace: i.Namespace,
+			Name:      expectedProxyConfigMapName(infra.Proxy.Name),
+		},
+	}
+
+	return i.deleteConfigMap(ctx, cm)
+}
+
+func expectedProxyConfigMapName(proxyName string) string {
+	cMapName := utils.GetHashedName(proxyName)
+	return fmt.Sprintf("%s-%s", config.EnvoyPrefix, cMapName)
+}
+
+// expectedRateLimitConfigMap returns the expected ConfigMap based on the provided infra.
+func (i *Infra) expectedRateLimitConfigMap(infra *ir.RateLimitInfra) *corev1.ConfigMap {
+	labels := rateLimitLabels()
+	data := make(map[string]string)
+
+	for _, config := range infra.Configs {
+		data[config.Name] = config.Config
+	}
+
+	return &corev1.ConfigMap{
+		ObjectMeta: metav1.ObjectMeta{
+			Namespace: i.Namespace,
+			Name:      rateLimitInfraName,
+			Labels:    labels,
+		},
+		Data: data,
+	}
+}
+
+// createOrUpdateRateLimitConfigMap creates a ConfigMap in the Kube api server based on the provided
+// infra, if it doesn't exist and updates it if it does.
+func (i *Infra) createOrUpdateRateLimitConfigMap(ctx context.Context, infra *ir.RateLimitInfra) error {
+	cm := i.expectedRateLimitConfigMap(infra)
+	return i.createOrUpdateConfigMap(ctx, cm)
+}
+
+// deleteProxyConfigMap deletes the Envoy ConfigMap in the kube api server, if it exists.
+func (i *Infra) deleteRateLimitConfigMap(ctx context.Context, _ *ir.RateLimitInfra) error {
+	cm := &corev1.ConfigMap{
+		ObjectMeta: metav1.ObjectMeta{
+			Namespace: i.Namespace,
+			Name:      rateLimitInfraName,
+		},
+	}
+
+	return i.deleteConfigMap(ctx, cm)
+}
+
+func (i *Infra) createOrUpdateConfigMap(ctx context.Context, cm *corev1.ConfigMap) error {
 	current := &corev1.ConfigMap{}
 	key := types.NamespacedName{
-		Namespace: i.Namespace,
-		Name:      expectedConfigMapName(infra.Proxy.Name),
+		Namespace: cm.Namespace,
+		Name:      cm.Name,
 	}
 
 	if err := i.Client.Get(ctx, key, current); err != nil {
 		// Create if not found.
 		if kerrors.IsNotFound(err) {
 			if err := i.Client.Create(ctx, cm); err != nil {
-				return nil, fmt.Errorf("failed to create configmap %s/%s: %w", cm.Namespace, cm.Name, err)
+				return fmt.Errorf("failed to create configmap %s/%s: %w", cm.Namespace, cm.Name, err)
 			}
 		}
 	} else {
 		// Update if current value is different.
 		if !reflect.DeepEqual(cm.Data, current.Data) {
 			if err := i.Client.Update(ctx, cm); err != nil {
-				return nil, fmt.Errorf("failed to update configmap %s/%s: %w", cm.Namespace, cm.Name, err)
+				return fmt.Errorf("failed to update configmap %s/%s: %w", cm.Namespace, cm.Name, err)
 			}
 		}
 	}
 
-	return cm, nil
+	return nil
 }
 
-// deleteConfigMap deletes the Envoy ConfigMap in the kube api server, if it exists.
-func (i *Infra) deleteConfigMap(ctx context.Context, infra *ir.Infra) error {
-	cm := &corev1.ConfigMap{
-		ObjectMeta: metav1.ObjectMeta{
-			Namespace: i.Namespace,
-			Name:      expectedConfigMapName(infra.Proxy.Name),
-		},
-	}
-
+func (i *Infra) deleteConfigMap(ctx context.Context, cm *corev1.ConfigMap) error {
 	if err := i.Client.Delete(ctx, cm); err != nil {
 		if kerrors.IsNotFound(err) {
 			return nil
@@ -117,8 +168,3 @@ func (i *Infra) deleteConfigMap(ctx context.Context, infra *ir.Infra) error {
 
 	return nil
 }
-
-func expectedConfigMapName(proxyName string) string {
-	cMapName := utils.GetHashedName(proxyName)
-	return fmt.Sprintf("%s-%s", config.EnvoyPrefix, cMapName)
-}
diff --git a/internal/infrastructure/kubernetes/deployment.go b/internal/infrastructure/kubernetes/deployment.go
index 350377d9f..ba31cd627 100644
--- a/internal/infrastructure/kubernetes/deployment.go
+++ b/internal/infrastructure/kubernetes/deployment.go
@@ -50,6 +50,13 @@ const (
 	envoyAdminPort = 19000
 	// envoyAdminAccessLogPath is the path used to expose admin access log.
 	envoyAdminAccessLogPath = "/dev/null"
+
+	// rateLimitInfraName is the name for rate-limit resources.
+	rateLimitInfraName = "envoy-ratelimit"
+	// rateLimitInfraHTTPPort is the http port that the rate limit service listens on.
+	rateLimitInfraHTTPPort = 8080
+	// rateLimitInfraImage is the container image for the rate limit service.
+	rateLimitInfraImage = "envoyproxy/ratelimit:latest"
 )
 
 //go:embed bootstrap.yaml.tpl
@@ -100,14 +107,14 @@ func (b *bootstrapConfig) render() error {
 	return nil
 }
 
-func expectedDeploymentName(proxyName string) string {
+func expectedProxyDeploymentName(proxyName string) string {
 	deploymentName := utils.GetHashedName(proxyName)
 	return fmt.Sprintf("%s-%s", config.EnvoyPrefix, deploymentName)
 }
 
-// expectedDeployment returns the expected Deployment based on the provided infra.
-func (i *Infra) expectedDeployment(infra *ir.Infra) (*appsv1.Deployment, error) {
-	containers, err := expectedContainers(infra)
+// expectedProxyDeployment returns the expected Deployment based on the provided infra.
+func (i *Infra) expectedProxyDeployment(infra *ir.Infra) (*appsv1.Deployment, error) {
+	containers, err := expectedProxyContainers(infra)
 	if err != nil {
 		return nil, err
 	}
@@ -118,6 +125,7 @@ func (i *Infra) expectedDeployment(infra *ir.Infra) (*appsv1.Deployment, error)
 		return nil, fmt.Errorf("missing owning gateway labels")
 	}
 
+	selector := getSelector(labels)
 	// Get the EnvoyProxy config to configure the ret.
 	provider := infra.GetProxyInfra().GetProxyConfig().GetProvider()
 	if provider.Type != egcfgv1a1.ProviderTypeKubernetes {
@@ -132,19 +140,19 @@ func (i *Infra) expectedDeployment(infra *ir.Infra) (*appsv1.Deployment, error)
 		},
 		ObjectMeta: metav1.ObjectMeta{
 			Namespace: i.Namespace,
-			Name:      expectedDeploymentName(infra.Proxy.Name),
+			Name:      expectedProxyDeploymentName(infra.Proxy.Name),
 			Labels:    labels,
 		},
 		Spec: appsv1.DeploymentSpec{
 			Replicas: deployCfg.Replicas,
-			Selector: envoySelector(infra.GetProxyInfra().GetProxyMetadata().Labels),
+			Selector: selector,
 			Template: corev1.PodTemplateSpec{
 				ObjectMeta: metav1.ObjectMeta{
-					Labels: envoySelector(infra.GetProxyInfra().GetProxyMetadata().Labels).MatchLabels,
+					Labels: selector.MatchLabels,
 				},
 				Spec: corev1.PodSpec{
 					Containers:                    containers,
-					ServiceAccountName:            expectedServiceAccountName(infra.Proxy.Name),
+					ServiceAccountName:            expectedProxyServiceAccountName(infra.Proxy.Name),
 					AutomountServiceAccountToken:  pointer.Bool(false),
 					TerminationGracePeriodSeconds: pointer.Int64(int64(300)),
 					DNSPolicy:                     corev1.DNSClusterFirst,
@@ -164,7 +172,7 @@ func (i *Infra) expectedDeployment(infra *ir.Infra) (*appsv1.Deployment, error)
 							VolumeSource: corev1.VolumeSource{
 								ConfigMap: &corev1.ConfigMapVolumeSource{
 									LocalObjectReference: corev1.LocalObjectReference{
-										Name: expectedConfigMapName(infra.Proxy.Name),
+										Name: expectedProxyConfigMapName(infra.Proxy.Name),
 									},
 									Items: []corev1.KeyToPath{
 										{
@@ -190,7 +198,7 @@ func (i *Infra) expectedDeployment(infra *ir.Infra) (*appsv1.Deployment, error)
 	return ret, nil
 }
 
-func expectedContainers(infra *ir.Infra) ([]corev1.Container, error) {
+func expectedProxyContainers(infra *ir.Infra) ([]corev1.Container, error) {
 	ports := []corev1.ContainerPort{
 		{
 			Name:          "http",
@@ -275,20 +283,172 @@ func expectedContainers(infra *ir.Infra) ([]corev1.Container, error) {
 	return containers, nil
 }
 
-// createDeployment creates a Deployment in the kube api server based on the provided
+// createOrUpdateProxyDeployment creates a Deployment in the kube api server based on the provided
 // infra, if it doesn't exist and updates it if it does.
-func (i *Infra) createOrUpdateDeployment(ctx context.Context, infra *ir.Infra) error {
-	deploy, err := i.expectedDeployment(infra)
+func (i *Infra) createOrUpdateProxyDeployment(ctx context.Context, infra *ir.Infra) error {
+	deploy, err := i.expectedProxyDeployment(infra)
 	if err != nil {
 		return err
 	}
+	return i.createOrUpdateDeployment(ctx, deploy)
+}
+
+// deleteProxyDeployment deletes the Envoy Deployment in the kube api server, if it exists.
+func (i *Infra) deleteProxyDeployment(ctx context.Context, infra *ir.Infra) error {
+	deploy := &appsv1.Deployment{
+		ObjectMeta: metav1.ObjectMeta{
+			Namespace: i.Namespace,
+			Name:      expectedProxyDeploymentName(infra.Proxy.Name),
+		},
+	}
+
+	return i.deleteDeployment(ctx, deploy)
+}
+
+// expectedRateLimitDeployment returns the expected rate limit Deployment based on the provided infra.
+func (i *Infra) expectedRateLimitDeployment(infra *ir.RateLimitInfra) *appsv1.Deployment {
+	containers := expectedRateLimitContainers(infra)
+	labels := rateLimitLabels()
+	selector := getSelector(labels)
+
+	ret := &appsv1.Deployment{
+		TypeMeta: metav1.TypeMeta{
+			Kind:       "Deployment",
+			APIVersion: "apps/v1",
+		},
+		ObjectMeta: metav1.ObjectMeta{
+			Namespace: i.Namespace,
+			Name:      rateLimitInfraName,
+			Labels:    labels,
+		},
+		Spec: appsv1.DeploymentSpec{
+			Replicas: pointer.Int32(int32(1)),
+			Selector: selector,
+			Template: corev1.PodTemplateSpec{
+				ObjectMeta: metav1.ObjectMeta{
+					Labels: selector.MatchLabels,
+				},
+				Spec: corev1.PodSpec{
+					Containers:                    containers,
+					ServiceAccountName:            rateLimitInfraName,
+					AutomountServiceAccountToken:  pointer.Bool(false),
+					TerminationGracePeriodSeconds: pointer.Int64(int64(300)),
+					DNSPolicy:                     corev1.DNSClusterFirst,
+					RestartPolicy:                 corev1.RestartPolicyAlways,
+					SchedulerName:                 "default-scheduler",
+					Volumes: []corev1.Volume{
+						{
+							Name: rateLimitInfraName,
+							VolumeSource: corev1.VolumeSource{
+								ConfigMap: &corev1.ConfigMapVolumeSource{
+									LocalObjectReference: corev1.LocalObjectReference{
+										Name: rateLimitInfraName,
+									},
+									DefaultMode: pointer.Int32(int32(420)),
+									Optional:    pointer.Bool(false),
+								},
+							},
+						},
+					},
+				},
+			},
+		},
+	}
+
+	return ret
+}
 
+func expectedRateLimitContainers(infra *ir.RateLimitInfra) []corev1.Container {
+	ports := []corev1.ContainerPort{
+		{
+			Name:          "http",
+			ContainerPort: rateLimitInfraHTTPPort,
+			Protocol:      corev1.ProtocolTCP,
+		},
+	}
+
+	containers := []corev1.Container{
+		{
+			Name:            rateLimitInfraName,
+			Image:           rateLimitInfraImage,
+			ImagePullPolicy: corev1.PullIfNotPresent,
+			Command: []string{
+				"/bin/ratelimit",
+			},
+			Env: []corev1.EnvVar{
+				{
+					Name:  "REDIS_SOCKET_TYPE",
+					Value: "tcp",
+				},
+				{
+					Name:  "REDIS_URL",
+					Value: infra.Backend.Redis.URL,
+				},
+				{
+					Name:  "RUNTIME_ROOT",
+					Value: "/data",
+				},
+				{Name: "RUNTIME_SUBDIRECTORY",
+					Value: "ratelimit",
+				},
+				{
+					Name:  "RUNTIME_IGNOREDOTFILES",
+					Value: "true",
+				},
+				{
+					Name:  "RUNTIME_WATCH_ROOT",
+					Value: "false",
+				},
+				{
+					Name:  "LOG_LEVEL",
+					Value: "info",
+				},
+				{
+					Name:  "USE_STATSD",
+					Value: "false",
+				},
+			},
+			Ports: ports,
+			VolumeMounts: []corev1.VolumeMount{
+				{
+					Name:      rateLimitInfraName,
+					MountPath: "/data/ratelimit/config",
+					ReadOnly:  true,
+				},
+			},
+			TerminationMessagePolicy: corev1.TerminationMessageReadFile,
+			TerminationMessagePath:   "/dev/termination-log",
+		},
+	}
+
+	return containers
+}
+
+// createOrUpdateRateLimitDeployment creates a Deployment in the kube api server based on the provided
+// infra, if it doesn't exist and updates it if it does.
+func (i *Infra) createOrUpdateRateLimitDeployment(ctx context.Context, infra *ir.RateLimitInfra) error {
+	deploy := i.expectedRateLimitDeployment(infra)
+	return i.createOrUpdateDeployment(ctx, deploy)
+}
+
+// deleteRateLimitDeployment deletes the Envoy RateLimit Deployment in the kube api server, if it exists.
+func (i *Infra) deleteRateLimitDeployment(ctx context.Context, _ *ir.RateLimitInfra) error {
+	deploy := &appsv1.Deployment{
+		ObjectMeta: metav1.ObjectMeta{
+			Namespace: i.Namespace,
+			Name:      rateLimitInfraName,
+		},
+	}
+
+	return i.deleteDeployment(ctx, deploy)
+}
+
+func (i *Infra) createOrUpdateDeployment(ctx context.Context, deploy *appsv1.Deployment) error {
 	current := &appsv1.Deployment{}
 	key := types.NamespacedName{
-		Namespace: i.Namespace,
-		Name:      expectedDeploymentName(infra.Proxy.Name),
+		Namespace: deploy.Namespace,
+		Name:      deploy.Name,
 	}
-
 	if err := i.Client.Get(ctx, key, current); err != nil {
 		// Create if not found.
 		if kerrors.IsNotFound(err) {
@@ -310,21 +470,12 @@ func (i *Infra) createOrUpdateDeployment(ctx context.Context, infra *ir.Infra) e
 	return nil
 }
 
-// deleteDeployment deletes the Envoy Deployment in the kube api server, if it exists.
-func (i *Infra) deleteDeployment(ctx context.Context, infra *ir.Infra) error {
-	deploy := &appsv1.Deployment{
-		ObjectMeta: metav1.ObjectMeta{
-			Namespace: i.Namespace,
-			Name:      expectedDeploymentName(infra.Proxy.Name),
-		},
-	}
-
+func (i *Infra) deleteDeployment(ctx context.Context, deploy *appsv1.Deployment) error {
 	if err := i.Client.Delete(ctx, deploy); err != nil {
 		if kerrors.IsNotFound(err) {
 			return nil
 		}
 		return fmt.Errorf("failed to delete deployment %s/%s: %w", deploy.Namespace, deploy.Name, err)
 	}
-
 	return nil
 }
diff --git a/internal/infrastructure/kubernetes/infra.go b/internal/infrastructure/kubernetes/infra.go
index efce6a777..3d297ea4e 100644
--- a/internal/infrastructure/kubernetes/infra.go
+++ b/internal/infrastructure/kubernetes/infra.go
@@ -32,8 +32,8 @@ func NewInfra(cli client.Client, cfg *config.Server) *Infra {
 	}
 }
 
-// CreateOrUpdateInfra creates the managed kube infra, if it doesn't exist.
-func (i *Infra) CreateOrUpdateInfra(ctx context.Context, infra *ir.Infra) error {
+// CreateOrUpdateProxyInfra creates the managed kube infra, if it doesn't exist.
+func (i *Infra) CreateOrUpdateProxyInfra(ctx context.Context, infra *ir.Infra) error {
 	if infra == nil {
 		return errors.New("infra ir is nil")
 	}
@@ -42,44 +42,44 @@ func (i *Infra) CreateOrUpdateInfra(ctx context.Context, infra *ir.Infra) error
 		return errors.New("infra proxy ir is nil")
 	}
 
-	if err := i.createOrUpdateServiceAccount(ctx, infra); err != nil {
+	if err := i.createOrUpdateProxyServiceAccount(ctx, infra); err != nil {
 		return err
 	}
 
-	if _, err := i.createOrUpdateConfigMap(ctx, infra); err != nil {
+	if err := i.createOrUpdateProxyConfigMap(ctx, infra); err != nil {
 		return err
 	}
 
-	if err := i.createOrUpdateDeployment(ctx, infra); err != nil {
+	if err := i.createOrUpdateProxyDeployment(ctx, infra); err != nil {
 		return err
 	}
 
-	if err := i.createOrUpdateService(ctx, infra); err != nil {
+	if err := i.createOrUpdateProxyService(ctx, infra); err != nil {
 		return err
 	}
 
 	return nil
 }
 
-// DeleteInfra removes the managed kube infra, if it doesn't exist.
-func (i *Infra) DeleteInfra(ctx context.Context, infra *ir.Infra) error {
+// DeleteProxyInfra removes the managed kube infra, if it doesn't exist.
+func (i *Infra) DeleteProxyInfra(ctx context.Context, infra *ir.Infra) error {
 	if infra == nil {
 		return errors.New("infra ir is nil")
 	}
 
-	if err := i.deleteService(ctx, infra); err != nil {
+	if err := i.deleteProxyService(ctx, infra); err != nil {
 		return err
 	}
 
-	if err := i.deleteDeployment(ctx, infra); err != nil {
+	if err := i.deleteProxyDeployment(ctx, infra); err != nil {
 		return err
 	}
 
-	if err := i.deleteConfigMap(ctx, infra); err != nil {
+	if err := i.deleteProxyConfigMap(ctx, infra); err != nil {
 		return err
 	}
 
-	if err := i.deleteServiceAccount(ctx, infra); err != nil {
+	if err := i.deleteProxyServiceAccount(ctx, infra); err != nil {
 		return err
 	}
 
@@ -88,12 +88,50 @@ func (i *Infra) DeleteInfra(ctx context.Context, infra *ir.Infra) error {
 
 // CreateOrUpdateRateLimitInfra creates the managed kube rate limit infra, if it doesn't exist.
 func (i *Infra) CreateOrUpdateRateLimitInfra(ctx context.Context, infra *ir.RateLimitInfra) error {
-	// TODO
+	if infra == nil {
+		return errors.New("ratelimit infra ir is nil")
+	}
+
+	if err := i.deleteRateLimitService(ctx, infra); err != nil {
+		return err
+	}
+
+	if err := i.deleteRateLimitDeployment(ctx, infra); err != nil {
+		return err
+	}
+
+	if err := i.deleteRateLimitConfigMap(ctx, infra); err != nil {
+		return err
+	}
+
+	if err := i.deleteRateLimitServiceAccount(ctx, infra); err != nil {
+		return err
+	}
+
 	return nil
 }
 
 // DeleteRateLimitInfra removes the managed kube infra, if it doesn't exist.
 func (i *Infra) DeleteRateLimitInfra(ctx context.Context, infra *ir.RateLimitInfra) error {
-	// TODO
+	if infra == nil {
+		return errors.New("ratelimit infra ir is nil")
+	}
+	if err := i.createOrUpdateRateLimitServiceAccount(ctx, infra); err != nil {
+		return err
+	}
+
+	if err := i.createOrUpdateRateLimitConfigMap(ctx, infra); err != nil {
+		return err
+	}
+
+	if err := i.createOrUpdateRateLimitDeployment(ctx, infra); err != nil {
+		return err
+	}
+
+	if err := i.createOrUpdateRateLimitService(ctx, infra); err != nil {
+		return err
+	}
+
 	return nil
+
 }
diff --git a/internal/infrastructure/kubernetes/labels.go b/internal/infrastructure/kubernetes/labels.go
index 263bb9d97..dfe26074e 100644
--- a/internal/infrastructure/kubernetes/labels.go
+++ b/internal/infrastructure/kubernetes/labels.go
@@ -16,11 +16,18 @@ func envoyAppLabel() map[string]string {
 	}
 }
 
-// envoySelector returns a label selector used to select resources
+// rateLimitLabels returns the labels used for all envoy rate limit resources.
+func rateLimitLabels() map[string]string {
+	return map[string]string{
+		"app.gateway.envoyproxy.io/name": rateLimitInfraName,
+	}
+}
+
+// getSelector returns a label selector used to select resources
 // based on the provided lbls.
-func envoySelector(extraLbls map[string]string) *metav1.LabelSelector {
+func getSelector(labels map[string]string) *metav1.LabelSelector {
 	return &metav1.LabelSelector{
-		MatchLabels: envoyLabels(extraLbls),
+		MatchLabels: labels,
 	}
 }
 
diff --git a/internal/infrastructure/kubernetes/service.go b/internal/infrastructure/kubernetes/service.go
index d69e3fab5..10740bb1f 100644
--- a/internal/infrastructure/kubernetes/service.go
+++ b/internal/infrastructure/kubernetes/service.go
@@ -22,13 +22,13 @@ import (
 	"github.com/envoyproxy/gateway/internal/provider/utils"
 )
 
-func expectedServiceName(proxyName string) string {
+func expectedProxyServiceName(proxyName string) string {
 	svcName := utils.GetHashedName(proxyName)
 	return fmt.Sprintf("%s-%s", config.EnvoyPrefix, svcName)
 }
 
-// expectedService returns the expected Service based on the provided infra.
-func (i *Infra) expectedService(infra *ir.Infra) (*corev1.Service, error) {
+// expectedproxyService returns the expected Service based on the provided infra.
+func (i *Infra) expectedProxyService(infra *ir.Infra) (*corev1.Service, error) {
 	var ports []corev1.ServicePort
 	for _, listener := range infra.Proxy.Listeners {
 		for _, port := range listener.Ports {
@@ -56,13 +56,13 @@ func (i *Infra) expectedService(infra *ir.Infra) (*corev1.Service, error) {
 	svc := &corev1.Service{
 		ObjectMeta: metav1.ObjectMeta{
 			Namespace: i.Namespace,
-			Name:      expectedServiceName(infra.Proxy.Name),
+			Name:      expectedProxyServiceName(infra.Proxy.Name),
 			Labels:    labels,
 		},
 		Spec: corev1.ServiceSpec{
 			Type:            corev1.ServiceTypeLoadBalancer,
 			Ports:           ports,
-			Selector:        envoySelector(infra.GetProxyInfra().GetProxyMetadata().Labels).MatchLabels,
+			Selector:        getSelector(labels).MatchLabels,
 			SessionAffinity: corev1.ServiceAffinityNone,
 			// Preserve the client source IP and avoid a second hop for LoadBalancer.
 			ExternalTrafficPolicy: corev1.ServiceExternalTrafficPolicyTypeLocal,
@@ -72,10 +72,10 @@ func (i *Infra) expectedService(infra *ir.Infra) (*corev1.Service, error) {
 	return svc, nil
 }
 
-// createOrUpdateService creates a Service in the kube api server based on the provided infra,
+// createOrUpdateproxyService creates a Service in the kube api server based on the provided infra,
 // if it doesn't exist or updates it if it does.
-func (i *Infra) createOrUpdateService(ctx context.Context, infra *ir.Infra) error {
-	svc, err := i.expectedService(infra)
+func (i *Infra) createOrUpdateProxyService(ctx context.Context, infra *ir.Infra) error {
+	svc, err := i.expectedProxyService(infra)
 	if err != nil {
 		return fmt.Errorf("failed to generate expected service: %w", err)
 	}
@@ -83,7 +83,7 @@ func (i *Infra) createOrUpdateService(ctx context.Context, infra *ir.Infra) erro
 	current := &corev1.Service{}
 	key := types.NamespacedName{
 		Namespace: i.Namespace,
-		Name:      expectedServiceName(infra.Proxy.Name),
+		Name:      expectedProxyServiceName(infra.Proxy.Name),
 	}
 
 	if err := i.Client.Get(ctx, key, current); err != nil {
@@ -107,15 +107,98 @@ func (i *Infra) createOrUpdateService(ctx context.Context, infra *ir.Infra) erro
 	return nil
 }
 
-// deleteService deletes the Envoy Service in the kube api server, if it exists.
-func (i *Infra) deleteService(ctx context.Context, infra *ir.Infra) error {
+// deleteProxyService deletes the Envoy Service in the kube api server, if it exists.
+func (i *Infra) deleteProxyService(ctx context.Context, infra *ir.Infra) error {
 	svc := &corev1.Service{
 		ObjectMeta: metav1.ObjectMeta{
 			Namespace: i.Namespace,
-			Name:      expectedServiceName(infra.Proxy.Name),
+			Name:      expectedProxyServiceName(infra.Proxy.Name),
 		},
 	}
 
+	return i.deleteService(ctx, svc)
+}
+
+// expectedRateLimitInfraService returns the expected rate limit Service based on the provided infra.
+func (i *Infra) expectedRateLimitService(_ *ir.RateLimitInfra) *corev1.Service {
+	ports := []corev1.ServicePort{
+		{
+			Name:       "http",
+			Protocol:   corev1.ProtocolTCP,
+			Port:       rateLimitInfraHTTPPort,
+			TargetPort: intstr.IntOrString{IntVal: rateLimitInfraHTTPPort},
+		},
+	}
+
+	labels := rateLimitLabels()
+
+	svc := &corev1.Service{
+		ObjectMeta: metav1.ObjectMeta{
+			Namespace: i.Namespace,
+			Name:      rateLimitInfraName,
+			Labels:    labels,
+		},
+		Spec: corev1.ServiceSpec{
+			Type:            corev1.ServiceTypeLoadBalancer,
+			Ports:           ports,
+			Selector:        getSelector(labels).MatchLabels,
+			SessionAffinity: corev1.ServiceAffinityNone,
+			// Preserve the client source IP and avoid a second hop for LoadBalancer.
+			ExternalTrafficPolicy: corev1.ServiceExternalTrafficPolicyTypeLocal,
+		},
+	}
+
+	return svc
+}
+
+// createOrUpdateRateLimitService creates a Service in the kube api server based on the provided infra,
+// if it doesn't exist or updates it if it does.
+func (i *Infra) createOrUpdateRateLimitService(ctx context.Context, infra *ir.RateLimitInfra) error {
+	svc := i.expectedRateLimitService(infra)
+	return i.createOrUpdateService(ctx, svc)
+}
+
+// deleteRateLimitService deletes the rate limit Service in the kube api server, if it exists.
+func (i *Infra) deleteRateLimitService(ctx context.Context, _ *ir.RateLimitInfra) error {
+	svc := &corev1.Service{
+		ObjectMeta: metav1.ObjectMeta{
+			Namespace: i.Namespace,
+			Name:      rateLimitInfraName,
+		},
+	}
+
+	return i.deleteService(ctx, svc)
+}
+
+func (i *Infra) createOrUpdateService(ctx context.Context, svc *corev1.Service) error {
+	current := &corev1.Service{}
+	key := types.NamespacedName{
+		Namespace: svc.Namespace,
+		Name:      svc.Name,
+	}
+
+	if err := i.Client.Get(ctx, key, current); err != nil {
+		// Create if not found.
+		if kerrors.IsNotFound(err) {
+			if err := i.Client.Create(ctx, svc); err != nil {
+				return fmt.Errorf("failed to create service %s/%s: %w",
+					svc.Namespace, svc.Name, err)
+			}
+		}
+	} else {
+		// Update if current value is different.
+		if !reflect.DeepEqual(svc.Spec, current.Spec) {
+			if err := i.Client.Update(ctx, svc); err != nil {
+				return fmt.Errorf("failed to update service %s/%s: %w",
+					svc.Namespace, svc.Name, err)
+			}
+		}
+	}
+
+	return nil
+}
+
+func (i *Infra) deleteService(ctx context.Context, svc *corev1.Service) error {
 	if err := i.Client.Delete(ctx, svc); err != nil {
 		if kerrors.IsNotFound(err) {
 			return nil
diff --git a/internal/infrastructure/kubernetes/serviceaccount.go b/internal/infrastructure/kubernetes/serviceaccount.go
index 97110c1d2..11dc0d2d8 100644
--- a/internal/infrastructure/kubernetes/serviceaccount.go
+++ b/internal/infrastructure/kubernetes/serviceaccount.go
@@ -20,13 +20,13 @@ import (
 	"github.com/envoyproxy/gateway/internal/provider/utils"
 )
 
-func expectedServiceAccountName(proxyName string) string {
+func expectedProxyServiceAccountName(proxyName string) string {
 	svcActName := utils.GetHashedName(proxyName)
 	return fmt.Sprintf("%s-%s", config.EnvoyPrefix, svcActName)
 }
 
-// expectedServiceAccount returns the expected proxy serviceAccount.
-func (i *Infra) expectedServiceAccount(infra *ir.Infra) (*corev1.ServiceAccount, error) {
+// expectedProxyServiceAccount returns the expected proxy serviceAccount.
+func (i *Infra) expectedProxyServiceAccount(infra *ir.Infra) (*corev1.ServiceAccount, error) {
 	// Set the labels based on the owning gateway name.
 	labels := envoyLabels(infra.GetProxyInfra().GetProxyMetadata().Labels)
 	if len(labels[gatewayapi.OwningGatewayNamespaceLabel]) == 0 || len(labels[gatewayapi.OwningGatewayNameLabel]) == 0 {
@@ -40,24 +40,74 @@ func (i *Infra) expectedServiceAccount(infra *ir.Infra) (*corev1.ServiceAccount,
 		},
 		ObjectMeta: metav1.ObjectMeta{
 			Namespace: i.Namespace,
-			Name:      expectedServiceAccountName(infra.Proxy.Name),
+			Name:      expectedProxyServiceAccountName(infra.Proxy.Name),
 			Labels:    labels,
 		},
 	}, nil
 }
 
-// createOrUpdateServiceAccount creates the Envoy ServiceAccount in the kube api server,
+// createOrUpdateProxyServiceAccount creates the Envoy ServiceAccount in the kube api server,
 // if it doesn't exist and updates it if it does.
-func (i *Infra) createOrUpdateServiceAccount(ctx context.Context, infra *ir.Infra) error {
-	sa, err := i.expectedServiceAccount(infra)
+func (i *Infra) createOrUpdateProxyServiceAccount(ctx context.Context, infra *ir.Infra) error {
+	sa, err := i.expectedProxyServiceAccount(infra)
 	if err != nil {
 		return err
 	}
+	return i.createOrUpdateServiceAccount(ctx, sa)
+}
+
+// deleteProxyServiceAccount deletes the Envoy ServiceAccount in the kube api server,
+// if it exists.
+func (i *Infra) deleteProxyServiceAccount(ctx context.Context, infra *ir.Infra) error {
+	sa := &corev1.ServiceAccount{
+		ObjectMeta: metav1.ObjectMeta{
+			Namespace: i.Namespace,
+			Name:      expectedProxyServiceAccountName(infra.Proxy.Name),
+		},
+	}
+
+	return i.deleteServiceAccount(ctx, sa)
+}
 
+// expectedRateLimitServiceAccount returns the expected ratelimit serviceAccount.
+func (i *Infra) expectedRateLimitServiceAccount(_ *ir.RateLimitInfra) *corev1.ServiceAccount {
+	return &corev1.ServiceAccount{
+		TypeMeta: metav1.TypeMeta{
+			Kind:       "ServiceAccount",
+			APIVersion: "v1",
+		},
+		ObjectMeta: metav1.ObjectMeta{
+			Namespace: i.Namespace,
+			Name:      rateLimitInfraName,
+		},
+	}
+}
+
+// createOrUpdateRateLimitServiceAccount creates the Envoy RateLimit ServiceAccount in the kube api server,
+// if it doesn't exist and updates it if it does.
+func (i *Infra) createOrUpdateRateLimitServiceAccount(ctx context.Context, infra *ir.RateLimitInfra) error {
+	sa := i.expectedRateLimitServiceAccount(infra)
+	return i.createOrUpdateServiceAccount(ctx, sa)
+}
+
+// deleteRateLimitServiceAccount deletes the Envoy RateLimit ServiceAccount in the kube api server,
+// if it exists.
+func (i *Infra) deleteRateLimitServiceAccount(ctx context.Context, _ *ir.RateLimitInfra) error {
+	sa := &corev1.ServiceAccount{
+		ObjectMeta: metav1.ObjectMeta{
+			Namespace: i.Namespace,
+			Name:      rateLimitInfraName,
+		},
+	}
+
+	return i.deleteServiceAccount(ctx, sa)
+}
+
+func (i *Infra) createOrUpdateServiceAccount(ctx context.Context, sa *corev1.ServiceAccount) error {
 	current := &corev1.ServiceAccount{}
 	key := types.NamespacedName{
-		Namespace: i.Namespace,
-		Name:      expectedServiceAccountName(infra.Proxy.Name),
+		Namespace: sa.Namespace,
+		Name:      sa.Name,
 	}
 
 	if err := i.Client.Get(ctx, key, current); err != nil {
@@ -80,15 +130,7 @@ func (i *Infra) createOrUpdateServiceAccount(ctx context.Context, infra *ir.Infr
 	return nil
 }
 
-// deleteServiceAccount deletes the Envoy ServiceAccount in the kube api server,
-// if it exists.
-func (i *Infra) deleteServiceAccount(ctx context.Context, infra *ir.Infra) error {
-	sa := &corev1.ServiceAccount{
-		ObjectMeta: metav1.ObjectMeta{
-			Namespace: i.Namespace,
-			Name:      expectedServiceAccountName(infra.Proxy.Name),
-		},
-	}
+func (i *Infra) deleteServiceAccount(ctx context.Context, sa *corev1.ServiceAccount) error {
 	if err := i.Client.Delete(ctx, sa); err != nil {
 		if kerrors.IsNotFound(err) {
 			return nil
diff --git a/internal/infrastructure/manager.go b/internal/infrastructure/manager.go
index 94cf8c553..f281a732f 100644
--- a/internal/infrastructure/manager.go
+++ b/internal/infrastructure/manager.go
@@ -23,10 +23,10 @@ var _ Manager = (*kubernetes.Infra)(nil)
 
 // Manager provides the scaffolding for managing infrastructure.
 type Manager interface {
-	// CreateOrUpdateInfra creates or updates infra.
-	CreateOrUpdateInfra(ctx context.Context, infra *ir.Infra) error
-	// DeleteInfra deletes infra.
-	DeleteInfra(ctx context.Context, infra *ir.Infra) error
+	// CreateOrUpdateProxyInfra creates or updates infra.
+	CreateOrUpdateProxyInfra(ctx context.Context, infra *ir.Infra) error
+	// DeleteProxyInfra deletes infra.
+	DeleteProxyInfra(ctx context.Context, infra *ir.Infra) error
 	// CreateOrUpdateRateLimitInfra creates or updates rate limit infra.
 	CreateOrUpdateRateLimitInfra(ctx context.Context, infra *ir.RateLimitInfra) error
 	// DeleteRateLimitInfra deletes rate limit infra.
diff --git a/internal/infrastructure/runner/runner.go b/internal/infrastructure/runner/runner.go
index 10c122e19..514a6ea34 100644
--- a/internal/infrastructure/runner/runner.go
+++ b/internal/infrastructure/runner/runner.go
@@ -41,25 +41,29 @@ func (r *Runner) Start(ctx context.Context) error {
 	if err != nil {
 		r.Logger.Error(err, "failed to create new manager")
 	}
-	go r.subscribeToInfraIR(ctx)
-	go r.subscribeToRateLimitInfraIR(ctx)
+	go r.subscribeToProxyInfraIR(ctx)
+
+	// subscribe to rate limit infra IR if ratelimit has been enabled in the config.
+	if r.EnvoyGateway.RateLimit != nil {
+		go r.subscribeToRateLimitInfraIR(ctx)
+	}
 	r.Logger.Info("started")
 	return nil
 }
 
-func (r *Runner) subscribeToInfraIR(ctx context.Context) {
+func (r *Runner) subscribeToProxyInfraIR(ctx context.Context) {
 	// Subscribe to resources
 	message.HandleSubscription(r.InfraIR.Subscribe(ctx),
 		func(update message.Update[string, *ir.Infra]) {
 			val := update.Value
 
 			if update.Delete {
-				if err := r.mgr.DeleteInfra(ctx, val); err != nil {
+				if err := r.mgr.DeleteProxyInfra(ctx, val); err != nil {
 					r.Logger.Error(err, "failed to delete infra")
 				}
 			} else {
 				// Manage the proxy infra.
-				if err := r.mgr.CreateOrUpdateInfra(ctx, val); err != nil {
+				if err := r.mgr.CreateOrUpdateProxyInfra(ctx, val); err != nil {
 					r.Logger.Error(err, "failed to create new infra")
 				}
 			}


Test patch:
diff --git a/docs/latest/user/http-redirect.md b/docs/latest/user/http-redirect.md
index c11b346b5..5b6588535 100644
--- a/docs/latest/user/http-redirect.md
+++ b/docs/latest/user/http-redirect.md
@@ -5,10 +5,13 @@ HTTPRoute rules cannot use both filter types at once. Currently, Envoy Gateway o
 [HTTPRoute filters][] which consist of `RequestRedirect` and `RequestHeaderModifier` at the time of this writing. To
 learn more about HTTP routing, refer to the [Gateway API documentation][].
 
-Follow the steps from the [Secure Gateways](secure-gateways.md) to install Envoy Gateway and the example manifest. Do
-not proceed until you can curl the example backend from the Quickstart guide using HTTPS.
+## Prerequisites
+
+Follow the steps from the [Secure Gateways](secure-gateways.md) to install Envoy Gateway and the example manifest.
+Before proceeding, you should be able to query the example backend using HTTPS.
 
 ## Redirects
+
 Redirects return HTTP 3XX responses to a client, instructing it to retrieve a different resource. A
 [`RequestRedirect` filter][req_filter] instructs Gateways to emit a redirect response to requests that match the rule.
 For example, to issue a permanent redirect (301) from HTTP to HTTPS, configure `requestRedirect.statusCode=301` and
@@ -68,6 +71,7 @@ If you followed the steps in the [Secure Gateways](secure-gateways.md) guide, yo
 location.
 
 ## Path Redirects
+
 Path redirects use an HTTP Path Modifier to replace either entire paths or path prefixes. For example, the HTTPRoute
 below will issue a 302 redirect to all `path.redirect.example` requests whose path begins with `/get` to `/status/200`.
 
diff --git a/docs/latest/user/http-request-headers.md b/docs/latest/user/http-request-headers.md
index 0fe3b627d..b51bf946c 100644
--- a/docs/latest/user/http-request-headers.md
+++ b/docs/latest/user/http-request-headers.md
@@ -1,16 +1,26 @@
 # HTTP Request Headers
 
-The [HTTPRoute][] resource can modify the headers of a request before forwarding it to the upstream service. HTTPRoute rules cannot use both filter types at once. Currently, Envoy Gateway only supports __core__
-[HTTPRoute filters][] which consist of `RequestRedirect` and `RequestHeaderModifier` at the time of this writing. To
-learn more about HTTP routing, refer to the [Gateway API documentation][].
+The [HTTPRoute][] resource can modify the headers of a request before forwarding it to the upstream service. HTTPRoute
+rules cannot use both filter types at once. Currently, Envoy Gateway only supports __core__ [HTTPRoute filters][] which
+consist of `RequestRedirect` and `RequestHeaderModifier` at the time of this writing. To learn more about HTTP routing,
+refer to the [Gateway API documentation][].
 
-A [`RequestHeaderModifier` filter][req_filter] instructs Gateways to modify the headers in requests that match the rule before forwarding the request upstream. Note that the `RequestHeaderModifier` filter will only modify headers before the request is sent from Envoy to the upstream service and will not affect response headers returned to the downstream client.
+A [`RequestHeaderModifier` filter][req_filter] instructs Gateways to modify the headers in requests that match the rule
+before forwarding the request upstream. Note that the `RequestHeaderModifier` filter will only modify headers before the
+request is sent from Envoy to the upstream service and will not affect response headers returned to the downstream
+client.
 
-Follow the steps from the [Quickstart Guide](QUICKSTART.md) to install Envoy Gateway and then install the example resources used for this guide.
+## Prerequisites
+
+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.
 
 ## Adding Request Headers
 
-The `RequestHeaderModifier` filter can add new headers to a request before it is sent to the upstream. If the request does not have the header configured by the filter, then that header will be added to the request. If the request already has the header configured by the filter, then the value of the header in the filter will be appended to the value of the header in the request.
+The `RequestHeaderModifier` filter can add new headers to a request before it is sent to the upstream. If the request
+does not have the header configured by the filter, then that header will be added to the request. If the request already
+has the header configured by the filter, then the value of the header in the filter will be appended to the value of the
+header in the request.
 
 ```shell
 cat <<EOF | kubectl apply -f -
@@ -55,8 +65,9 @@ Get the Gateway's address:
 export GATEWAY_HOST=$(kubectl get gateway/eg -o jsonpath='{.status.addresses[0].value}')
 ```
 
-Querying `headers.example/get` should result in a `200` response from the example Gateway and the output from the example
-app should indicate that the upstream example app received the header `add-header` with the value: `something,foo`
+Querying `headers.example/get` should result in a `200` response from the example Gateway and the output from the
+example app should indicate that the upstream example app received the header `add-header` with the value:
+`something,foo`
 
 ```console
 $ curl -vvv --header "Host: headers.example" "http://${GATEWAY_HOST}:8080/get" --header "add-header: something"
@@ -89,7 +100,10 @@ $ curl -vvv --header "Host: headers.example" "http://${GATEWAY_HOST}:8080/get" -
 
 ## Setting Request Headers
 
-Setting headers is similar to adding headers. If the request does not have the header configured by the filter, then it will be added, but unlike [adding request headers](#adding-request-headers) which will append the value of the header if the request already contains it, setting a header will cause the value to be replaced by the value configured in the filter.
+Setting headers is similar to adding headers. If the request does not have the header configured by the filter, then it
+will be added, but unlike [adding request headers](#adding-request-headers) which will append the value of the header if
+the request already contains it, setting a header will cause the value to be replaced by the value configured in the
+filter.
 
 ```shell
 cat <<EOF | kubectl apply -f -
@@ -122,8 +136,9 @@ spec:
 EOF
 ```
 
-Querying `headers.example/get` should result in a `200` response from the example Gateway and the output from the example
-app should indicate that the upstream example app received the header `add-header` with the original value `something` replaced by `foo`.
+Querying `headers.example/get` should result in a `200` response from the example Gateway and the output from the
+example app should indicate that the upstream example app received the header `add-header` with the original value
+`something` replaced by `foo`.
 
 ```console
 $ curl -vvv --header "Host: headers.example" "http://${GATEWAY_HOST}:8080/get" --header "set-header: something"
@@ -156,7 +171,10 @@ $ curl -vvv --header "Host: headers.example" "http://${GATEWAY_HOST}:8080/get" -
 
 Headers can be removed from a request by simply supplying a list of header names.
 
-Setting headers is similar to adding headers. If the request does not have the header configured by the filter, then it will be added, but unlike [adding request headers](#adding-request-headers) which will append the value of the header if the request already contains it, setting a header will cause the value to be replaced by the value configured in the filter.
+Setting headers is similar to adding headers. If the request does not have the header configured by the filter, then it
+will be added, but unlike [adding request headers](#adding-request-headers) which will append the value of the header if
+the request already contains it, setting a header will cause the value to be replaced by the value configured in the
+filter.
 
 ```shell
 cat <<EOF | kubectl apply -f -
@@ -187,9 +205,9 @@ spec:
 EOF
 ```
 
-Querying `headers.example/get` should result in a `200` response from the example Gateway and the output from the example
-app should indicate that the upstream example app received the header `add-header`, but the header `remove-header` that was sent by curl
-was removed before the upstream received the request.
+Querying `headers.example/get` should result in a `200` response from the example Gateway and the output from the
+example app should indicate that the upstream example app received the header `add-header`, but the header
+`remove-header` that was sent by curl was removed before the upstream received the request.
 
 ```console
 $ curl -vvv --header "Host: headers.example" "http://${GATEWAY_HOST}:8080/get" --header "add-header: something" --header "remove-header: foo"
diff --git a/docs/latest/user/http-response-headers.md b/docs/latest/user/http-response-headers.md
index 3d156b931..6b0a4b5b0 100644
--- a/docs/latest/user/http-response-headers.md
+++ b/docs/latest/user/http-response-headers.md
@@ -1,14 +1,24 @@
 # HTTP Response Headers
 
-The [HTTPRoute][] resource can modify the headers of a response before responding it to the downstream service. To learn more about HTTP routing, refer to the [Gateway API documentation][].
+The [HTTPRoute][] resource can modify the headers of a response before responding it to the downstream service. To learn
+more about HTTP routing, refer to the [Gateway API documentation][].
 
-A [`ResponseHeaderModifier` filter][req_filter] instructs Gateways to modify the headers in responses that match the rule before responding to the downstream. Note that the `ResponseHeaderModifier` filter will only modify headers before the response is returned from Envoy to the downstream client and will not affect request headers forwarding to the upstream service.
+A [`ResponseHeaderModifier` filter][req_filter] instructs Gateways to modify the headers in responses that match the
+rule before responding to the downstream. Note that the `ResponseHeaderModifier` filter will only modify headers before
+the response is returned from Envoy to the downstream client and will not affect request headers forwarding to the
+upstream service.
 
-Follow the steps from the [Quickstart Guide](QUICKSTART.md) to install Envoy Gateway and then install the example resources used for this guide.
+## Prerequisites
+
+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.
 
 ## Adding Response Headers
 
-The `ResponseHeaderModifier` filter can add new headers to a response before it is sent to the upstream. If the response does not have the header configured by the filter, then that header will be added to the response. If the response already has the header configured by the filter, then the value of the header in the filter will be appended to the value of the header in the response.
+The `ResponseHeaderModifier` filter can add new headers to a response before it is sent to the upstream. If the response
+does not have the header configured by the filter, then that header will be added to the response. If the response
+already has the header configured by the filter, then the value of the header in the filter will be appended to the
+value of the header in the response.
 
 ```shell
 cat <<EOF | kubectl apply -f -
@@ -53,8 +63,8 @@ Get the Gateway's address:
 export GATEWAY_HOST=$(kubectl get gateway/eg -o jsonpath='{.status.addresses[0].value}')
 ```
 
-Querying `headers.example/get` should result in a `200` response from the example Gateway and the output from the example
-app should indicate that the downstream client received the header `add-header` with the value: `foo`
+Querying `headers.example/get` should result in a `200` response from the example Gateway and the output from the
+example app should indicate that the downstream client received the header `add-header` with the value: `foo`
 
 ```console
 $ curl -vvv --header "Host: headers.example" "http://${GATEWAY_HOST}:8080/get" -H 'X-Echo-Set-Header: X-Foo: value1'
@@ -88,7 +98,10 @@ $ curl -vvv --header "Host: headers.example" "http://${GATEWAY_HOST}:8080/get" -
 
 ## Setting Response Headers
 
-Setting headers is similar to adding headers. If the response does not have the header configured by the filter, then it will be added, but unlike [adding response headers](#adding-response-headers) which will append the value of the header if the response already contains it, setting a header will cause the value to be replaced by the value configured in the filter.
+Setting headers is similar to adding headers. If the response does not have the header configured by the filter, then it
+will be added, but unlike [adding response headers](#adding-response-headers) which will append the value of the header
+if the response already contains it, setting a header will cause the value to be replaced by the value configured in the
+filter.
 
 ```shell
 cat <<EOF | kubectl apply -f -
@@ -121,8 +134,9 @@ spec:
 EOF
 ```
 
-Querying `headers.example/get` should result in a `200` response from the example Gateway and the output from the example
-app should indicate that the downstream client received the header `set-header` with the original value `value1` replaced by `foo`.
+Querying `headers.example/get` should result in a `200` response from the example Gateway and the output from the
+example app should indicate that the downstream client received the header `set-header` with the original value `value1`
+replaced by `foo`.
 
 ```console
 $ curl -vvv --header "Host: headers.example" "http://${GATEWAY_HOST}:8080/get" -H 'X-Echo-Set-Header: set-header: value1'
@@ -156,7 +170,10 @@ $ curl -vvv --header "Host: headers.example" "http://${GATEWAY_HOST}:8080/get" -
 
 Headers can be removed from a response by simply supplying a list of header names.
 
-Setting headers is similar to adding headers. If the response does not have the header configured by the filter, then it will be added, but unlike [adding response headers](#adding-response-headers) which will append the value of the header if the response already contains it, setting a header will cause the value to be replaced by the value configured in the filter.
+Setting headers is similar to adding headers. If the response does not have the header configured by the filter, then it
+will be added, but unlike [adding response headers](#adding-response-headers) which will append the value of the header
+if the response already contains it, setting a header will cause the value to be replaced by the value configured in the
+filter.
 
 ```shell
 cat <<EOF | kubectl apply -f -
@@ -187,9 +204,9 @@ spec:
 EOF
 ```
 
-Querying `headers.example/get` should result in a `200` response from the example Gateway and the output from the example
-app should indicate that the header `remove-header` that was sent by curl
-was removed before the upstream received the response.
+Querying `headers.example/get` should result in a `200` response from the example Gateway and the output from the
+example app should indicate that the header `remove-header` that was sent by curl was removed before the upstream
+received the response.
 
 ```console
 $ curl -vvv --header "Host: headers.example" "http://${GATEWAY_HOST}:8080/get" -H 'X-Echo-Set-Header: remove-header: value1'
diff --git a/docs/latest/user/http-routing.md b/docs/latest/user/http-routing.md
index 67137f571..f0a2fa187 100644
--- a/docs/latest/user/http-routing.md
+++ b/docs/latest/user/http-routing.md
@@ -5,19 +5,36 @@ Kubernetes backends. Currently, the only supported backend supported by Envoy Ga
 shows how to route traffic based on host, header, and path fields and forward the traffic to different Kubernetes
 Services. To learn more about HTTP routing, refer to the [Gateway API documentation][].
 
-Follow the steps from the [Quickstart Guide](quickstart.md) to install Envoy Gateway and then install the example
-resources used for this guide.
+## Prerequisites
+
+Install Envoy Gateway:
+
+```shell
+kubectl apply -f https://github.com/envoyproxy/gateway/releases/download/latest/install.yaml
+```
+
+Wait for Envoy Gateway to become available:
+
+```shell
+kubectl wait --timeout=5m -n envoy-gateway-system deployment/envoy-gateway --for=condition=Available
+```
+
+## Installation
+
+Install the HTTP routing example resources:
 
 ```shell
 kubectl apply -f https://raw.githubusercontent.com/envoyproxy/gateway/latest/examples/kubernetes/http-routing.yaml
 ```
 
 The manifest installs a [GatewayClass][], [Gateway][], four Deployments, four Services, and three HTTPRoute resources.
+The GatewayClass is a cluster-scoped resource that represents a class of Gateways that can be instantiated.
 
-The GatewayClass is a cluster-scoped resource that represents a class of Gateways that can be instantiated. Envoy
-Gateway is configured by default to manage GatewayClasses with
+__Note:__ Envoy Gateway is configured by default to manage a GatewayClass with
 `controllerName: gateway.envoyproxy.io/gatewayclass-controller`.
 
+## Verification
+
 Check the status of the GatewayClass:
 
 ```shell
@@ -51,8 +68,11 @@ kubectl get httproutes --selector=example=http-routing -o yaml
 ```
 
 The status for each HTTPRoute should surface "Accepted=True" and a `parentRef` that references the example Gateway.
-The `example-route` matches any traffic for "example.com" and forwards it to the "example-svc" Service. Before testing
-HTTP routing to the `example-svc` backend, get the Gateway's address.
+The `example-route` matches any traffic for "example.com" and forwards it to the "example-svc" Service.
+
+## Testing the Configuration
+
+Before testing HTTP routing to the `example-svc` backend, get the Gateway's address.
 
 ```shell
 export GATEWAY_HOST=$(kubectl get gateway/example-gateway -o jsonpath='{.status.addresses[0].value}')
diff --git a/docs/latest/user/http-traffic-splitting.md b/docs/latest/user/http-traffic-splitting.md
index 562bbcc0f..a3f8fe60b 100644
--- a/docs/latest/user/http-traffic-splitting.md
+++ b/docs/latest/user/http-traffic-splitting.md
@@ -1,8 +1,13 @@
 # HTTPRoute Traffic Splitting
 
-The [HTTPRoute][] resource allows one or more [backendRefs][] to be provided. Requests will be routed to these upstreams if they match the rules of the HTTPRoute. If an invalid backendRef is configured, then HTTP responses will be returned with status code `500` for all of the requests that would have been sent to that backend.
+The [HTTPRoute][] resource allows one or more [backendRefs][] to be provided. Requests will be routed to these upstreams
+if they match the rules of the HTTPRoute. If an invalid backendRef is configured, then HTTP responses will be returned
+with status code `500` for all requests that would have been sent to that backend.
 
-Follow the steps from the [Quickstart Guide](QUICKSTART.md) to install Envoy Gateway and then install the example resources used for this guide.
+## Installation
+
+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.
 
 ## Single backendRef
 
@@ -44,8 +49,9 @@ Get the Gateway's address:
 export GATEWAY_HOST=$(kubectl get gateway/eg -o jsonpath='{.status.addresses[0].value}')
 ```
 
-Querying `backends.example/get` should result in a `200` response from the example Gateway and the output from the example
-app should indicate which pod handled the request. There is only one pod in the deployment for the example app from the quickstart, so it will be the same on all subsequent requests.
+Querying `backends.example/get` should result in a `200` response from the example Gateway and the output from the
+example app should indicate which pod handled the request. There is only one pod in the deployment for the example app
+from the quickstart, so it will be the same on all subsequent requests.
 
 ```console
 $ curl -vvv --header "Host: backends.example" "http://${GATEWAY_HOST}:8080/get"
@@ -74,7 +80,8 @@ $ curl -vvv --header "Host: backends.example" "http://${GATEWAY_HOST}:8080/get"
 
 ## Multiple backendRefs
 
-If multiple backendRefs are configured, then traffic will be split between the backendRefs equally unless a weight is configured.
+If multiple backendRefs are configured, then traffic will be split between the backendRefs equally unless a weight is
+configured.
 
 First, create a second instance of the example app from the quickstart:
 
@@ -166,8 +173,9 @@ spec:
 EOF
 ```
 
-Querying `backends.example/get` should result in `200` responses from the example Gateway and the output from the example app
-that indicates which pod handled the request should switch between the first pod and the second one from the new deployment on subsequent requests.
+Querying `backends.example/get` should result in `200` responses from the example Gateway and the output from the
+example app that indicates which pod handled the request should switch between the first pod and the second one from the
+new deployment on subsequent requests.
 
 ```console
 $ curl -vvv --header "Host: backends.example" "http://${GATEWAY_HOST}:8080/get"
@@ -196,11 +204,16 @@ $ curl -vvv --header "Host: backends.example" "http://${GATEWAY_HOST}:8080/get"
 
 ## Weighted backendRefs
 
-If multiple backendRefs are configured and an un-even traffic split between the backends is desired, then the `weight` field can be used to control the weight of requests to each backend. If weight is not configured for a backendRef it is assumed to be `1`.
+If multiple backendRefs are configured and an un-even traffic split between the backends is desired, then the `weight`
+field can be used to control the weight of requests to each backend. If weight is not configured for a backendRef it is
+assumed to be `1`.
 
-The [weight field in a backendRef][backendRefs] controls the distribution of the traffic split. The proportion of requests to a single backendRef is calculated by dividing its `weight` by the sum of all backendRef weights in the HTTPRoute. The weight is not a percentage and the sum of all weights does not need to add up to 100.
+The [weight field in a backendRef][backendRefs] controls the distribution of the traffic split. The proportion of
+requests to a single backendRef is calculated by dividing its `weight` by the sum of all backendRef weights in the
+HTTPRoute. The weight is not a percentage and the sum of all weights does not need to add up to 100.
 
-The HTTPRoute below will configure the gateway to send 80% of the traffic to the backend service, and 20% to the backend-2 service.
+The HTTPRoute below will configure the gateway to send 80% of the traffic to the backend service, and 20% to the
+backend-2 service.
 
 ```shell
 cat <<EOF | kubectl apply -f -
@@ -236,13 +249,17 @@ EOF
 
 backendRefs can be considered invalid for the following reasons:
 
-- The `group` field is configured to something other than `""`. Currently, only the core API group (specified by omitting the group field or setting it to an empty string) is supported
-- The `kind` field is configured to anything other than `Service`. Envoy Gateway currently only supports Kubernetes Service backendRefs
+- The `group` field is configured to something other than `""`. Currently, only the core API group (specified by
+  omitting the group field or setting it to an empty string) is supported
+- The `kind` field is configured to anything other than `Service`. Envoy Gateway currently only supports Kubernetes
+  Service backendRefs
 - The backendRef configures a service with a `namespace` not permitted by any existing ReferenceGrants
 - The `port` field is not configured or is configured to a port that does not exist on the Service
 - The named Service configured by the backendRef cannot be found
 
-Modifying the above example to make the backend-2 backendRef invalid by using a port that does not exist on the Service will result in 80% of the traffic being sent to the backend service, and 20% of the traffic receiving a HTTP response with status code `500`.
+Modifying the above example to make the backend-2 backendRef invalid by using a port that does not exist on the Service
+will result in 80% of the traffic being sent to the backend service, and 20% of the traffic receiving an HTTP response
+with status code `500`.
 
 ```shell
 cat <<EOF | kubectl apply -f -
diff --git a/docs/latest/user/http-urlrewrite.md b/docs/latest/user/http-urlrewrite.md
index c2fa79e6a..bc465ccf8 100644
--- a/docs/latest/user/http-urlrewrite.md
+++ b/docs/latest/user/http-urlrewrite.md
@@ -1,14 +1,17 @@
 # HTTP URL Rewrite
 
-[HTTPURLRewriteFilter](https://gateway-api.sigs.k8s.io/references/spec/#gateway.networking.k8s.io/v1beta1.HTTPURLRewriteFilter) defines a filter that modifies a request during forwarding. At most one of these filters may be used on a Route rule. This MUST NOT be used on the same Route rule as a HTTPRequestRedirect filter.
+[HTTPURLRewriteFilter][] defines a filter that modifies a request during forwarding. At most one of these filters may be
+used on a Route rule. This MUST NOT be used on the same Route rule as a HTTPRequestRedirect filter.
 
-Follow the steps from the [Quickstart](quickstart.md) to install Envoy Gateway and the example manifest. Do
-not proceed until you can curl the example backend from the Quickstart guide using HTTP.
+## Prerequisites
+
+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.
 
 ## Rewrite URL Prefix Path
 
-You can configure to rewrite the prefix in the url like below. In this example, any curls to `http://${GATEWAY_HOST}:8080/get/xxx` will be
-rewrite to `http://${GATEWAY_HOST}:8080/replace/xxx`.
+You can configure to rewrite the prefix in the url like below. In this example, any curls to
+`http://${GATEWAY_HOST}:8080/get/xxx` will be rewritten to `http://${GATEWAY_HOST}:8080/replace/xxx`.
 
 ```shell
 cat <<EOF | kubectl apply -f -
@@ -49,7 +52,8 @@ Get the Gateway's address:
 export GATEWAY_HOST=$(kubectl get gateway/eg -o jsonpath='{.status.addresses[0].value}')
 ```
 
-Querying `http://${GATEWAY_HOST}:8080/get/origin/path` should rewrite to `http://${GATEWAY_HOST}:8080/replace/origin/path`.
+Querying `http://${GATEWAY_HOST}:8080/get/origin/path` should rewrite to
+`http://${GATEWAY_HOST}:8080/replace/origin/path`.
 
 ```console
 $ curl -L -vvv --header "Host: path.rewrite.example" "http://${GATEWAY_HOST}:8080/get/origin/path"
@@ -104,8 +108,9 @@ You can see that the `X-Envoy-Original-Path` is `/get/origin/path`, but the actu
 
 ## Rewrite URL Full Path
 
-You can configure to rewrite the fullpath in the url like below. In this example, any request sent to `http://${GATEWAY_HOST}:8080/get/origin/path/xxxx` will be
-rewritten to `http://${GATEWAY_HOST}:8080/force/replace/fullpath`.
+You can configure to rewrite the fullpath in the url like below. In this example, any request sent to
+`http://${GATEWAY_HOST}:8080/get/origin/path/xxxx` will be rewritten to
+`http://${GATEWAY_HOST}:8080/force/replace/fullpath`.
 
 ```shell
 cat <<EOF | kubectl apply -f -
@@ -141,7 +146,8 @@ The HTTPRoute status should indicate that it has been accepted and is bound to t
 kubectl get httproute/http-filter-url-rewrite -o yaml
 ```
 
-Querying `http://${GATEWAY_HOST}:8080/get/origin/path/extra` should rewrite the request to `http://${GATEWAY_HOST}:8080/force/replace/fullpath`.
+Querying `http://${GATEWAY_HOST}:8080/get/origin/path/extra` should rewrite the request to
+`http://${GATEWAY_HOST}:8080/force/replace/fullpath`.
 
 ```console
 $ curl -L -vvv --header "Host: path.rewrite.example" "http://${GATEWAY_HOST}:8080/get/origin/path/extra"
@@ -192,11 +198,13 @@ $ curl -L -vvv --header "Host: path.rewrite.example" "http://${GATEWAY_HOST}:808
 ...
 ```
 
-You can see that the `X-Envoy-Original-Path` is `/get/origin/path/extra`, but the actual path is `/force/replace/fullpath`.
+You can see that the `X-Envoy-Original-Path` is `/get/origin/path/extra`, but the actual path is
+`/force/replace/fullpath`.
 
 ## Rewrite Host Name
 
-You can configure to rewrite the hostname like below. In this example, any requests sent to `http://${GATEWAY_HOST}:8080/get` with `--header "Host: path.rewrite.example"` will rewrite host into `envoygateway.io`.
+You can configure to rewrite the hostname like below. In this example, any requests sent to
+`http://${GATEWAY_HOST}:8080/get` with `--header "Host: path.rewrite.example"` will rewrite host into `envoygateway.io`.
 
 ```shell
 cat <<EOF | kubectl apply -f -
@@ -230,7 +238,8 @@ The HTTPRoute status should indicate that it has been accepted and is bound to t
 kubectl get httproute/http-filter-url-rewrite -o yaml
 ```
 
-Querying `http://${GATEWAY_HOST}:8080/get` with `--header "Host: path.rewrite.example"` will rewrite host into `envoygateway.io`.
+Querying `http://${GATEWAY_HOST}:8080/get` with `--header "Host: path.rewrite.example"` will rewrite host into
+`envoygateway.io`.
 
 ```console
 $ curl -L -vvv --header "Host: path.rewrite.example" "http://${GATEWAY_HOST}:8080/get"
@@ -282,3 +291,5 @@ $ curl -L -vvv --header "Host: path.rewrite.example" "http://${GATEWAY_HOST}:808
 ```
 
 You can see that the `X-Forwarded-Host` is `path.rewrite.example`, but the actual host is `envoygateway.io`.
+
+[HTTPURLRewriteFilter]: https://gateway-api.sigs.k8s.io/references/spec/#gateway.networking.k8s.io/v1beta1.HTTPURLRewriteFilter
diff --git a/docs/latest/user/quickstart.md b/docs/latest/user/quickstart.md
index 2dbdde29c..147edb14e 100644
--- a/docs/latest/user/quickstart.md
+++ b/docs/latest/user/quickstart.md
@@ -6,7 +6,7 @@ This guide will help you get started with Envoy Gateway in a few simple steps.
 
 A Kubernetes cluster.
 
-__Note:__ Envoy Gateway is tested against Kubernetes v1.24.0.
+__Note:__ Refer to the [Compatibility Matrix](../intro/compatibility.rst) for supported Kubernetes versions.
 
 ## Installation
 
diff --git a/docs/latest/user/secure-gateways.md b/docs/latest/user/secure-gateways.md
index 8305b0165..cd5199495 100644
--- a/docs/latest/user/secure-gateways.md
+++ b/docs/latest/user/secure-gateways.md
@@ -5,11 +5,8 @@ testing and demonstration purposes only.
 
 ## Prerequisites
 
-- A Kubernetes cluster with `kubectl` context configured for the cluster.
 - OpenSSL to generate TLS assets.
 
-__Note:__ Envoy Gateway is tested against Kubernetes v1.24.
-
 ## Installation
 
 Follow the steps from the [Quickstart Guide](quickstart.md) to install Envoy Gateway and the example manifest.
diff --git a/docs/latest/user/tcp-routing.md b/docs/latest/user/tcp-routing.md
index dbf8c145c..c84e92c6f 100644
--- a/docs/latest/user/tcp-routing.md
+++ b/docs/latest/user/tcp-routing.md
@@ -1,14 +1,32 @@
 # TCP Routing
 
-[TCPRoute][] provides a way to route TCP requests. When combined with a Gateway listener, it can be used to forward connections on the port specified by the listener to a set of backends specified by the TCPRoute. To learn more about HTTP routing, refer to the [Gateway API documentation][].
+[TCPRoute][] provides a way to route TCP requests. When combined with a Gateway listener, it can be used to forward
+connections on the port specified by the listener to a set of backends specified by the TCPRoute. To learn more about
+HTTP routing, refer to the [Gateway API documentation][].
 
-Follow the steps to install Envoy Gateway and the example manifest.
+## Installation
 
-In this example, we have one Gateway resource and two TCPRoute resources that distribute the traffic with the following rules:
+Install Envoy Gateway:
+
+```shell
+kubectl apply -f https://github.com/envoyproxy/gateway/releases/download/latest/install.yaml
+```
+
+Wait for Envoy Gateway to become available:
+
+```shell
+kubectl wait --timeout=5m -n envoy-gateway-system deployment/envoy-gateway --for=condition=Available
+```
+
+## Configuration
+
+In this example, we have one Gateway resource and two TCPRoute resources that distribute the traffic with the following
+rules:
 
 All TCP streams on port `8088` of the Gateway are forwarded to port 3001 of `foo` Kubernetes Service.
 All TCP streams on port `8089` of the Gateway are forwarded to port 3002 of `bar` Kubernetes Service.
-In this example two TCP listeners will be applied to the Gateway in order to route them to two separate backend TCPRoutes, note that the protocol set for the listeners on the Gateway is TCP:
+In this example two TCP listeners will be applied to the Gateway in order to route them to two separate backend
+TCPRoutes, note that the protocol set for the listeners on the Gateway is TCP:
 
 Install the GatewayClass and a `tcp-gateway` Gateway first.
 
@@ -177,7 +195,8 @@ spec:
 EOF
 ```
 
-In the above example we separate the traffic for the two separate backend TCP Services by using the sectionName field in the parentRefs:
+In the above example we separate the traffic for the two separate backend TCP Services by using the sectionName field in
+the parentRefs:
 
 ``` yaml
 spec:
diff --git a/docs/latest/user/tls-passthrough.md b/docs/latest/user/tls-passthrough.md
index 500a38398..75a14d2a5 100644
--- a/docs/latest/user/tls-passthrough.md
+++ b/docs/latest/user/tls-passthrough.md
@@ -1,18 +1,17 @@
 # TLS Passthrough
 
-This guide will walk through the steps required to configure TLS Passthrough via Envoy Gateway. Unlike configuring Secure Gateways, where the Gateway terminates the client TLS connection, TLS Passthrough allows the application itself to terminate the TLS connection, while the Gateway routes the requests to the application based on SNI headers.
+This guide will walk through the steps required to configure TLS Passthrough via Envoy Gateway. Unlike configuring
+Secure Gateways, where the Gateway terminates the client TLS connection, TLS Passthrough allows the application itself
+to terminate the TLS connection, while the Gateway routes the requests to the application based on SNI headers.
 
 ## Prerequisites
 
-- A Kubernetes cluster with `kubectl` context configured for the cluster.
 - OpenSSL to generate TLS assets.
 
-__Note:__ Envoy Gateway is tested against Kubernetes v1.24.0.
-
 ## Installation
 
 Follow the steps from the [Quickstart Guide](quickstart.md) to install Envoy Gateway and the example manifest.
-Before proceeding, you should be able to curl the example backend using HTTP.
+Before proceeding, you should be able to query the example backend using HTTP.
 
 ## TLS Certificates
 
@@ -48,7 +47,8 @@ Deploy TLS Passthrough application Deployment, Service and TLSRoute:
 kubectl apply -f https://raw.githubusercontent.com/envoyproxy/gateway/latest/examples/kubernetes/tls-passthrough.yaml
 ```
 
-Patch the Gateway from the Quickstart guide to include a TLS listener that listens on port `6443` and is configured for TLS mode Passthrough:
+Patch the Gateway from the Quickstart guide to include a TLS listener that listens on port `6443` and is configured for
+TLS mode Passthrough:
 
 ```shell
 kubectl patch gateway eg --type=json --patch '[{
diff --git a/docs/latest/user/udp-routing.md b/docs/latest/user/udp-routing.md
index b2ae53b25..bf8c76717 100644
--- a/docs/latest/user/udp-routing.md
+++ b/docs/latest/user/udp-routing.md
@@ -1,21 +1,28 @@
 # UDP Routing
 
-The [UDPRoute](https://gateway-api.sigs.k8s.io/references/spec/#gateway.networking.k8s.io/v1alpha2.UDPRoute/)
-resource allows users to configure UDP routing by matching UDP traffic and forwarding it to
-Kubernetes backends. This guide will use CoreDNS example to walk you through the steps required to configure
- UDPRoute on Envoy Gateway.
+The [UDPRoute][] resource allows users to configure UDP routing by matching UDP traffic and forwarding it to Kubernetes
+backends. This guide will use CoreDNS example to walk you through the steps required to configure UDPRoute on Envoy
+Gateway.
 
-Note: UDPRoute allows Envoy Gateway to operate as a non-transparent proxy between a UDP client and server. 
-The lack of transparency means that the upstream server will see the source IP and port of the Gateway instead of the client. 
-For additional information, refer to Envoy's [UDP proxy documentation](https://www.envoyproxy.io/docs/envoy/latest/configuration/listeners/udp_filters/udp_proxy).
+__Note:__ UDPRoute allows Envoy Gateway to operate as a non-transparent proxy between a UDP client and server. The lack
+of transparency means that the upstream server will see the source IP and port of the Gateway instead of the client.
+For additional information, refer to Envoy's [UDP proxy documentation][].
 
 ## Prerequisites
 
-- A Kubernetes cluster with `kubectl` context configured for the cluster.
+Install Envoy Gateway:
 
-## Installation
+```shell
+kubectl apply -f https://github.com/envoyproxy/gateway/releases/download/latest/install.yaml
+```
 
-Follow the steps from the [Quickstart Guide](quickstart.md) to install Envoy Gateway.
+Wait for Envoy Gateway to become available:
+
+```shell
+kubectl wait --timeout=5m -n envoy-gateway-system deployment/envoy-gateway --for=condition=Available
+```
+
+## Installation
 
 Install CoreDNS in the Kubernetes cluster as the example backend. The installed CoreDNS is listening on
  UDP port 53 for DNS lookups.
@@ -55,7 +62,7 @@ Verify the Gateway status:
 kubectl get gateway/eg -o yaml
 ```
 
-## UDPRoute
+## Configuration
 
 Create a UDPRoute resource to route UDP traffic received on Gateway port 5300 to the CoredDNS backend.
 
@@ -142,3 +149,6 @@ kubectl delete udproute/coredns
 ## Next Steps
 
 Checkout the [Developer Guide](../dev/README.md) to get involved in the project.
+
+[UDPRoute]: https://gateway-api.sigs.k8s.io/references/spec/#gateway.networking.k8s.io/v1alpha2.UDPRoute
+[UDP proxy documentation]: https://www.envoyproxy.io/docs/envoy/latest/configuration/listeners/udp_filters/udp_proxy
diff --git a/internal/gatewayapi/testdata/httproute-with-valid-ratelimitfilter.in.yaml b/internal/gatewayapi/testdata/httproute-with-valid-ratelimitfilter.in.yaml
index 6aa58ec54..5640fd55a 100644
--- a/internal/gatewayapi/testdata/httproute-with-valid-ratelimitfilter.in.yaml
+++ b/internal/gatewayapi/testdata/httproute-with-valid-ratelimitfilter.in.yaml
@@ -54,6 +54,8 @@ rateLimitFilters:
         - headers:
           - name: x-user-id
             value: one
+          - name: x-org-id
+            type: Distinct
         limit:
           requests: 10
           unit: Hour
diff --git a/internal/gatewayapi/testdata/httproute-with-valid-ratelimitfilter.out.yaml b/internal/gatewayapi/testdata/httproute-with-valid-ratelimitfilter.out.yaml
index f5d34768c..1f565c680 100644
--- a/internal/gatewayapi/testdata/httproute-with-valid-ratelimitfilter.out.yaml
+++ b/internal/gatewayapi/testdata/httproute-with-valid-ratelimitfilter.out.yaml
@@ -85,6 +85,8 @@ xdsIR:
             - headerMatches:
               - name: "x-user-id"
                 exact: "one"
+              - name: "x-org-id"
+                distinct: true
               limit:
                 requests: 10
                 unit: Hour
diff --git a/internal/infrastructure/kubernetes/configmap_test.go b/internal/infrastructure/kubernetes/configmap_test.go
index c476d3a64..ec27b6f64 100644
--- a/internal/infrastructure/kubernetes/configmap_test.go
+++ b/internal/infrastructure/kubernetes/configmap_test.go
@@ -14,6 +14,7 @@ import (
 	corev1 "k8s.io/api/core/v1"
 	apiequality "k8s.io/apimachinery/pkg/api/equality"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"sigs.k8s.io/controller-runtime/pkg/client"
 	fakeclient "sigs.k8s.io/controller-runtime/pkg/client/fake"
 
 	"github.com/envoyproxy/gateway/internal/envoygateway"
@@ -22,7 +23,7 @@ import (
 	"github.com/envoyproxy/gateway/internal/ir"
 )
 
-func TestExpectedConfigMap(t *testing.T) {
+func TestExpectedProxyConfigMap(t *testing.T) {
 	// Setup the infra.
 	cli := fakeclient.NewClientBuilder().WithScheme(envoygateway.GetScheme()).WithObjects().Build()
 	cfg, err := config.New()
@@ -35,13 +36,13 @@ func TestExpectedConfigMap(t *testing.T) {
 
 	// An infra without Gateway owner labels should trigger
 	// an error.
-	_, err = kube.expectedConfigMap(infra)
+	_, err = kube.expectedProxyConfigMap(infra)
 	require.NotNil(t, err)
 
 	infra.Proxy.GetProxyMetadata().Labels[gatewayapi.OwningGatewayNamespaceLabel] = "default"
 	infra.Proxy.GetProxyMetadata().Labels[gatewayapi.OwningGatewayNameLabel] = infra.Proxy.Name
 
-	cm, err := kube.expectedConfigMap(infra)
+	cm, err := kube.expectedProxyConfigMap(infra)
 	require.NoError(t, err)
 
 	require.Equal(t, "envoy-test-74657374", cm.Name)
@@ -57,7 +58,7 @@ func TestExpectedConfigMap(t *testing.T) {
 	assert.True(t, apiequality.Semantic.DeepEqual(wantLabels, cm.Labels))
 }
 
-func TestCreateOrUpdateConfigMap(t *testing.T) {
+func TestCreateOrUpdateProxyConfigMap(t *testing.T) {
 	cfg, err := config.New()
 	require.NoError(t, err)
 	kube := NewInfra(nil, cfg)
@@ -123,17 +124,22 @@ func TestCreateOrUpdateConfigMap(t *testing.T) {
 			} else {
 				kube.Client = fakeclient.NewClientBuilder().WithScheme(envoygateway.GetScheme()).Build()
 			}
-			cm, err := kube.createOrUpdateConfigMap(context.Background(), infra)
+			err := kube.createOrUpdateProxyConfigMap(context.Background(), infra)
 			require.NoError(t, err)
-			require.Equal(t, tc.expect.Namespace, cm.Namespace)
-			require.Equal(t, tc.expect.Name, cm.Name)
-			assert.True(t, apiequality.Semantic.DeepEqual(tc.expect.Labels, cm.Labels))
-			require.Equal(t, tc.expect.Data, cm.Data)
+			actual := &corev1.ConfigMap{
+				ObjectMeta: metav1.ObjectMeta{
+					Namespace: tc.expect.Namespace,
+					Name:      tc.expect.Name,
+				},
+			}
+			require.NoError(t, kube.Client.Get(context.Background(), client.ObjectKeyFromObject(actual), actual))
+			require.Equal(t, tc.expect.Data, actual.Data)
+			assert.True(t, apiequality.Semantic.DeepEqual(tc.expect.Labels, actual.Labels))
 		})
 	}
 }
 
-func TestDeleteConfigMap(t *testing.T) {
+func TestDeleteConfigProxyMap(t *testing.T) {
 	cfg, err := config.New()
 	require.NoError(t, err)
 
@@ -173,7 +179,7 @@ func TestDeleteConfigMap(t *testing.T) {
 			t.Parallel()
 			cli := fakeclient.NewClientBuilder().WithScheme(envoygateway.GetScheme()).WithObjects(tc.current).Build()
 			kube := NewInfra(cli, cfg)
-			err := kube.deleteConfigMap(context.Background(), infra)
+			err := kube.deleteProxyConfigMap(context.Background(), infra)
 			require.NoError(t, err)
 		})
 	}
diff --git a/internal/infrastructure/kubernetes/deployment_test.go b/internal/infrastructure/kubernetes/deployment_test.go
index d71c87553..f812d6418 100644
--- a/internal/infrastructure/kubernetes/deployment_test.go
+++ b/internal/infrastructure/kubernetes/deployment_test.go
@@ -117,11 +117,11 @@ func TestExpectedDeployment(t *testing.T) {
 	infra.Proxy.GetProxyMetadata().Labels[gatewayapi.OwningGatewayNamespaceLabel] = "default"
 	infra.Proxy.GetProxyMetadata().Labels[gatewayapi.OwningGatewayNameLabel] = infra.Proxy.Name
 
-	deploy, err := kube.expectedDeployment(infra)
+	deploy, err := kube.expectedProxyDeployment(infra)
 	require.NoError(t, err)
 
 	// Check the deployment name is as expected.
-	assert.Equal(t, deploy.Name, expectedDeploymentName(infra.Proxy.Name))
+	assert.Equal(t, deploy.Name, expectedProxyDeploymentName(infra.Proxy.Name))
 
 	// Check container details, i.e. env vars, labels, etc. for the deployment are as expected.
 	container := checkContainer(t, deploy, envoyContainerName, true)
@@ -158,7 +158,7 @@ func TestExpectedDeployment(t *testing.T) {
 	repl := int32(2)
 	infra.Proxy.GetProxyConfig().GetProvider().GetKubeResourceProvider().EnvoyDeployment.Replicas = &repl
 
-	deploy, err = kube.expectedDeployment(infra)
+	deploy, err = kube.expectedProxyDeployment(infra)
 	require.NoError(t, err)
 
 	// Check the number of replicas is as expected.
@@ -185,7 +185,7 @@ func TestCreateOrUpdateDeployment(t *testing.T) {
 	infra.Proxy.GetProxyMetadata().Labels[gatewayapi.OwningGatewayNamespaceLabel] = "default"
 	infra.Proxy.GetProxyMetadata().Labels[gatewayapi.OwningGatewayNameLabel] = infra.Proxy.Name
 
-	deploy, err := kube.expectedDeployment(infra)
+	deploy, err := kube.expectedProxyDeployment(infra)
 	require.NoError(t, err)
 
 	testCases := []struct {
@@ -233,13 +233,13 @@ func TestCreateOrUpdateDeployment(t *testing.T) {
 			} else {
 				kube.Client = fakeclient.NewClientBuilder().WithScheme(envoygateway.GetScheme()).Build()
 			}
-			err := kube.createOrUpdateDeployment(context.Background(), tc.in)
+			err := kube.createOrUpdateProxyDeployment(context.Background(), tc.in)
 			require.NoError(t, err)
 
 			actual := &appsv1.Deployment{
 				ObjectMeta: metav1.ObjectMeta{
 					Namespace: kube.Namespace,
-					Name:      expectedDeploymentName(tc.in.Proxy.Name),
+					Name:      expectedProxyDeploymentName(tc.in.Proxy.Name),
 				},
 			}
 			require.NoError(t, kube.Client.Get(context.Background(), client.ObjectKeyFromObject(actual), actual))
@@ -248,7 +248,7 @@ func TestCreateOrUpdateDeployment(t *testing.T) {
 	}
 }
 
-func TestDeleteDeployment(t *testing.T) {
+func TestDeleteProxyDeployment(t *testing.T) {
 	testCases := []struct {
 		name   string
 		expect bool
@@ -268,7 +268,7 @@ func TestDeleteDeployment(t *testing.T) {
 				Namespace: "test",
 			}
 			infra := ir.NewInfra()
-			err := kube.deleteDeployment(context.Background(), infra)
+			err := kube.deleteProxyDeployment(context.Background(), infra)
 			require.NoError(t, err)
 		})
 	}
diff --git a/internal/infrastructure/kubernetes/infra_test.go b/internal/infrastructure/kubernetes/infra_test.go
index 26f037a81..5b8743e6b 100644
--- a/internal/infrastructure/kubernetes/infra_test.go
+++ b/internal/infrastructure/kubernetes/infra_test.go
@@ -21,7 +21,7 @@ import (
 	"github.com/envoyproxy/gateway/internal/ir"
 )
 
-func TestCreateInfra(t *testing.T) {
+func TestCreateProxyInfra(t *testing.T) {
 	// Infra with Gateway owner labels.
 	infraWithLabels := ir.NewInfra()
 	infraWithLabels.GetProxyInfra().GetProxyMetadata().Labels = envoyAppLabel()
@@ -66,7 +66,7 @@ func TestCreateInfra(t *testing.T) {
 				Namespace: "default",
 			}
 			// Create or update the proxy infra.
-			err := kube.CreateOrUpdateInfra(context.Background(), tc.in)
+			err := kube.CreateOrUpdateProxyInfra(context.Background(), tc.in)
 			if !tc.expect {
 				require.Error(t, err)
 			} else {
@@ -76,7 +76,7 @@ func TestCreateInfra(t *testing.T) {
 				sa := &corev1.ServiceAccount{
 					ObjectMeta: metav1.ObjectMeta{
 						Namespace: kube.Namespace,
-						Name:      expectedServiceAccountName(tc.in.Proxy.Name),
+						Name:      expectedProxyServiceAccountName(tc.in.Proxy.Name),
 					},
 				}
 				require.NoError(t, kube.Client.Get(context.Background(), client.ObjectKeyFromObject(sa), sa))
@@ -84,7 +84,7 @@ func TestCreateInfra(t *testing.T) {
 				cm := &corev1.ConfigMap{
 					ObjectMeta: metav1.ObjectMeta{
 						Namespace: kube.Namespace,
-						Name:      expectedConfigMapName(tc.in.Proxy.Name),
+						Name:      expectedProxyConfigMapName(tc.in.Proxy.Name),
 					},
 				}
 				require.NoError(t, kube.Client.Get(context.Background(), client.ObjectKeyFromObject(cm), cm))
@@ -92,7 +92,7 @@ func TestCreateInfra(t *testing.T) {
 				deploy := &appsv1.Deployment{
 					ObjectMeta: metav1.ObjectMeta{
 						Namespace: kube.Namespace,
-						Name:      expectedDeploymentName(tc.in.Proxy.Name),
+						Name:      expectedProxyDeploymentName(tc.in.Proxy.Name),
 					},
 				}
 				require.NoError(t, kube.Client.Get(context.Background(), client.ObjectKeyFromObject(deploy), deploy))
@@ -100,7 +100,7 @@ func TestCreateInfra(t *testing.T) {
 				svc := &corev1.Service{
 					ObjectMeta: metav1.ObjectMeta{
 						Namespace: kube.Namespace,
-						Name:      expectedServiceName(tc.in.Proxy.Name),
+						Name:      expectedProxyServiceName(tc.in.Proxy.Name),
 					},
 				}
 				require.NoError(t, kube.Client.Get(context.Background(), client.ObjectKeyFromObject(svc), svc))
@@ -109,7 +109,7 @@ func TestCreateInfra(t *testing.T) {
 	}
 }
 
-func TestDeleteInfra(t *testing.T) {
+func TestDeleteProxyInfra(t *testing.T) {
 	testCases := []struct {
 		name   string
 		in     *ir.Infra
@@ -134,7 +134,7 @@ func TestDeleteInfra(t *testing.T) {
 			kube := &Infra{
 				Client: fakeclient.NewClientBuilder().WithScheme(envoygateway.GetScheme()).Build(),
 			}
-			err := kube.DeleteInfra(context.Background(), tc.in)
+			err := kube.DeleteProxyInfra(context.Background(), tc.in)
 			if !tc.expect {
 				require.Error(t, err)
 			} else {
diff --git a/internal/infrastructure/kubernetes/labels_test.go b/internal/infrastructure/kubernetes/labels_test.go
index 621efaa87..8fcc39023 100644
--- a/internal/infrastructure/kubernetes/labels_test.go
+++ b/internal/infrastructure/kubernetes/labels_test.go
@@ -30,7 +30,7 @@ func TestEnvoyPodSelector(t *testing.T) {
 	for _, tc := range cases {
 		tc := tc
 		t.Run("", func(t *testing.T) {
-			got := envoySelector(tc.in)
+			got := getSelector(envoyLabels(tc.in))
 			require.Equal(t, tc.expected, got.MatchLabels)
 		})
 	}
diff --git a/internal/infrastructure/kubernetes/service_test.go b/internal/infrastructure/kubernetes/service_test.go
index f4500adc2..733bd6a36 100644
--- a/internal/infrastructure/kubernetes/service_test.go
+++ b/internal/infrastructure/kubernetes/service_test.go
@@ -66,7 +66,7 @@ func checkServiceHasLabels(t *testing.T, svc *corev1.Service, expected map[strin
 	t.Errorf("service has unexpected %q labels", svc.Labels)
 }
 
-func TestDesiredService(t *testing.T) {
+func TestDesiredProxyService(t *testing.T) {
 	cfg, err := config.New()
 	require.NoError(t, err)
 	cli := fakeclient.NewClientBuilder().WithScheme(envoygateway.GetScheme()).WithObjects().Build()
@@ -88,11 +88,11 @@ func TestDesiredService(t *testing.T) {
 			ContainerPort: 2443,
 		},
 	}
-	svc, err := kube.expectedService(infra)
+	svc, err := kube.expectedProxyService(infra)
 	require.NoError(t, err)
 
 	// Check the service name is as expected.
-	assert.Equal(t, svc.Name, expectedDeploymentName(infra.Proxy.Name))
+	assert.Equal(t, svc.Name, expectedProxyDeploymentName(infra.Proxy.Name))
 
 	checkServiceHasPort(t, svc, 80)
 	checkServiceHasPort(t, svc, 443)
@@ -110,7 +110,7 @@ func TestDesiredService(t *testing.T) {
 	}
 }
 
-func TestDeleteService(t *testing.T) {
+func TestDeleteProxyService(t *testing.T) {
 	testCases := []struct {
 		name string
 	}{
@@ -127,7 +127,7 @@ func TestDeleteService(t *testing.T) {
 				Namespace: "test",
 			}
 			infra := ir.NewInfra()
-			err := kube.deleteService(context.Background(), infra)
+			err := kube.deleteProxyService(context.Background(), infra)
 			require.NoError(t, err)
 		})
 	}
diff --git a/internal/infrastructure/kubernetes/serviceaccount_test.go b/internal/infrastructure/kubernetes/serviceaccount_test.go
index b9918e8a1..1f9127a92 100644
--- a/internal/infrastructure/kubernetes/serviceaccount_test.go
+++ b/internal/infrastructure/kubernetes/serviceaccount_test.go
@@ -25,7 +25,7 @@ import (
 	"github.com/envoyproxy/gateway/internal/ir"
 )
 
-func TestExpectedServiceAccount(t *testing.T) {
+func TestExpectedProxyServiceAccount(t *testing.T) {
 	cfg, err := config.New()
 	require.NoError(t, err)
 	cli := fakeclient.NewClientBuilder().WithScheme(envoygateway.GetScheme()).WithObjects().Build()
@@ -34,17 +34,17 @@ func TestExpectedServiceAccount(t *testing.T) {
 
 	// An infra without Gateway owner labels should trigger
 	// an error.
-	_, err = kube.expectedServiceAccount(infra)
+	_, err = kube.expectedProxyServiceAccount(infra)
 	require.NotNil(t, err)
 
 	infra.Proxy.GetProxyMetadata().Labels[gatewayapi.OwningGatewayNamespaceLabel] = "default"
 	infra.Proxy.GetProxyMetadata().Labels[gatewayapi.OwningGatewayNameLabel] = infra.Proxy.Name
 
-	sa, err := kube.expectedServiceAccount(infra)
+	sa, err := kube.expectedProxyServiceAccount(infra)
 	require.NoError(t, err)
 
 	// Check the serviceaccount name is as expected.
-	assert.Equal(t, sa.Name, expectedServiceAccountName(infra.Proxy.Name))
+	assert.Equal(t, sa.Name, expectedProxyServiceAccountName(infra.Proxy.Name))
 
 	wantLabels := envoyAppLabel()
 	wantLabels[gatewayapi.OwningGatewayNamespaceLabel] = "default"
@@ -52,7 +52,7 @@ func TestExpectedServiceAccount(t *testing.T) {
 	assert.True(t, apiequality.Semantic.DeepEqual(wantLabels, sa.Labels))
 }
 
-func TestCreateOrUpdateServiceAccount(t *testing.T) {
+func TestCreateOrUpdateProxyServiceAccount(t *testing.T) {
 	testCases := []struct {
 		name    string
 		ns      string
@@ -194,13 +194,13 @@ func TestCreateOrUpdateServiceAccount(t *testing.T) {
 			} else {
 				kube.Client = fakeclient.NewClientBuilder().WithScheme(envoygateway.GetScheme()).Build()
 			}
-			err := kube.createOrUpdateServiceAccount(context.Background(), tc.in)
+			err := kube.createOrUpdateProxyServiceAccount(context.Background(), tc.in)
 			require.NoError(t, err)
 
 			actual := &corev1.ServiceAccount{
 				ObjectMeta: metav1.ObjectMeta{
 					Namespace: kube.Namespace,
-					Name:      expectedServiceAccountName(tc.in.Proxy.Name),
+					Name:      expectedProxyServiceAccountName(tc.in.Proxy.Name),
 				},
 			}
 			require.NoError(t, kube.Client.Get(context.Background(), client.ObjectKeyFromObject(actual), actual))
@@ -211,7 +211,7 @@ func TestCreateOrUpdateServiceAccount(t *testing.T) {
 	}
 }
 
-func TestDeleteServiceAccount(t *testing.T) {
+func TestDeleteProxyServiceAccount(t *testing.T) {
 	testCases := []struct {
 		name   string
 		expect bool
@@ -230,7 +230,7 @@ func TestDeleteServiceAccount(t *testing.T) {
 				Namespace: "test",
 			}
 			infra := ir.NewInfra()
-			err := kube.deleteServiceAccount(context.Background(), infra)
+			err := kube.deleteProxyServiceAccount(context.Background(), infra)
 			require.NoError(t, err)
 		})
 	}