Skip to content

HTTP Traffic Splitting #261

New issue

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

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

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Oct 24, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

NGINX Kubernetes Gateway is an open-source project that provides an implementation of the [Gateway API](https://gateway-api.sigs.k8s.io/) using [NGINX](https://nginx.org/) as the data plane. The goal of this project is to implement the core Gateway APIs -- `Gateway`, `GatewayClass`, `HTTPRoute`, `TCPRoute`, `TLSRoute`, and `UDPRoute` -- to configure an HTTP or TCP/UDP load balancer, reverse-proxy, or API gateway for applications running on Kubernetes. NGINX Kubernetes Gateway is currently under development and supports a subset of the Gateway API.

For a list of supported Gateway API resources and features, see the [Gateway API Compatibility](docs/gateway-api-compatibility.md.md) doc.
For a list of supported Gateway API resources and features, see the [Gateway API Compatibility](docs/gateway-api-compatibility.md) doc.

> Warning: This project is actively in development (beta feature state) and should not be deployed in a production environment.
> All APIs, SDKs, designs, and packages are subject to change.
Expand Down
4 changes: 4 additions & 0 deletions deploy/manifests/nginx-gateway.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,8 @@ spec:
volumes:
- name: nginx-config
emptyDir: { }
- name: var-lib-nginx
emptyDir: { }
- name: njs-modules
configMap:
name: njs-modules
Expand Down Expand Up @@ -118,5 +120,7 @@ spec:
volumeMounts:
- name: nginx-config
mountPath: /etc/nginx
- name: var-lib-nginx
mountPath: /var/lib/nginx
- name: njs-modules
mountPath: /usr/lib/nginx/modules/njs
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ Fields:
* `type` - supported.
* `requestRedirect` - supported except for the experimental `path` field. If multiple filters with `requestRedirect` are configured, NGINX Kubernetes Gateway will choose the first one and ignore the rest.
* `requestHeaderModifier`, `requestMirror`, `urlRewrite`, `extensionRef` - not supported.
* `backendRefs` - partially supported. Only a single backend ref without support for `weight`. Backend ref `filters` are not supported.
* `backendRefs` - partially supported. Backend ref `filters` are not supported.
* `status`
* `parents`
* `parentRef` - supported.
Expand Down
101 changes: 101 additions & 0 deletions examples/traffic-splitting/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
# Example

In this example we will deploy NGINX Kubernetes Gateway and configure traffic splitting for a simple cafe application.
We will use `HTTPRoute` resources to split traffic between two versions of the application -- `coffee-v1` and `coffee-v2`.

## Running the Example

## 1. Deploy NGINX Kubernetes Gateway

1. Follow the [installation instructions](/docs/installation.md) to deploy NGINX Gateway.

1. Save the public IP address of NGINX Kubernetes Gateway into a shell variable:

```
GW_IP=XXX.YYY.ZZZ.III
```

1. Save the port of NGINX Kubernetes Gateway:

```
GW_PORT=<port number>
```

## 2. Deploy the Coffee Application

1. Create the Cafe Deployments and Services:

```
kubectl apply -f cafe.yaml
```

1. Check that the Pods are running in the `default` namespace:

```
kubectl -n default get pods
NAME READY STATUS RESTARTS AGE
coffee-v1-7c57c576b-rfjsh 1/1 Running 0 21m
coffee-v2-698f66dc46-vcb6r 1/1 Running 0 21m
```

## 3. Configure Routing

1. Create the `Gateway`:

```
kubectl apply -f gateway.yaml
```

1. Create the `HTTPRoute` resources:

```
kubectl apply -f cafe-route.yaml
```

This `HTTPRoute` resource defines a route for the path `/coffee` that sends 80% of the requests to `coffee-v1` and 20% to `coffee-v2`.
In this example, we use 80 and 20; however, the weights are calculated proportionally and do not need to sum to 100.
For example, the weights of 8 and 2, 16 and 4, or 32 and 8 all evaluate to the same relative proportions.

## 4. Test the Application

To access the application, we will use `curl` to send requests to `/coffee`:

```
curl --resolve cafe.example.com:$GW_PORT:$GW_IP http://cafe.example.com:$GW_PORT/coffee
```

80% of the responses will come from `coffee-v1`:

```
Server address: 10.12.0.18:80
Server name: coffee-v1-7c57c576b-rfjsh
```

20% of the responses will come from `coffee-v2`:

```
Server address: 10.12.0.19:80
Server name: coffee-v2-698f66dc46-vcb6r
```

### 5. Modify the Traffic Split Configuration

Let's shift more of the traffic to `coffee-v2`. To do this we will update the `HTTPRoute` resource and change the weight
of the `coffee-v2` backend to 80. Backends with equal weights will receive an equal share of traffic.

1. Apply the updated `HTTPRoute` resource:

```
kubectl apply -f cafe-route-equal-weight.yaml
```

2. Test the application again:

```
curl --resolve cafe.example.com:$GW_PORT:$GW_IP http://cafe.example.com:$GW_PORT/coffee
```

The responses will now be split evenly between `coffee-v1` and `coffee-v2`.

We can continue modifying the weights of the backends to shift more and more traffic to `coffee-v2`. If there's an issue
with `coffee-v2`, we can quickly shift traffic back to `coffee-v1`.
22 changes: 22 additions & 0 deletions examples/traffic-splitting/cafe-route-equal-weight.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
apiVersion: gateway.networking.k8s.io/v1beta1
kind: HTTPRoute
metadata:
name: cafe-route
spec:
parentRefs:
- name: gateway
sectionName: http
hostnames:
- "cafe.example.com"
rules:
- matches:
- path:
type: PathPrefix
value: /coffee
backendRefs:
- name: coffee-v1
port: 80
weight: 80
- name: coffee-v2
port: 80
weight: 80
22 changes: 22 additions & 0 deletions examples/traffic-splitting/cafe-route.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
apiVersion: gateway.networking.k8s.io/v1beta1
kind: HTTPRoute
metadata:
name: cafe-route
spec:
parentRefs:
- name: gateway
sectionName: http
hostnames:
- "cafe.example.com"
rules:
- matches:
- path:
type: PathPrefix
value: /coffee
backendRefs:
- name: coffee-v1
port: 80
weight: 80
- name: coffee-v2
port: 80
weight: 20
65 changes: 65 additions & 0 deletions examples/traffic-splitting/cafe.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: coffee-v1
spec:
replicas: 1
selector:
matchLabels:
app: coffee-v1
template:
metadata:
labels:
app: coffee-v1
spec:
containers:
- name: coffee-v1
image: nginxdemos/nginx-hello:plain-text
ports:
- containerPort: 8080
---
apiVersion: v1
kind: Service
metadata:
name: coffee-v1
spec:
ports:
- port: 80
targetPort: 8080
protocol: TCP
name: http
selector:
app: coffee-v1
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: coffee-v2
spec:
replicas: 1
selector:
matchLabels:
app: coffee-v2
template:
metadata:
labels:
app: coffee-v2
spec:
containers:
- name: coffee-v2
image: nginxdemos/nginx-hello:plain-text
ports:
- containerPort: 8080
---
apiVersion: v1
kind: Service
metadata:
name: coffee-v2
spec:
ports:
- port: 80
targetPort: 8080
protocol: TCP
name: http
selector:
app: coffee-v2
12 changes: 12 additions & 0 deletions examples/traffic-splitting/gateway.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
apiVersion: gateway.networking.k8s.io/v1beta1
kind: Gateway
metadata:
name: gateway
labels:
domain: k8s-gateway.nginx.org
spec:
gatewayClassName: nginx
listeners:
- name: http
port: 80
protocol: HTTP
18 changes: 18 additions & 0 deletions internal/nginx/config/execute.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package config

import (
"bytes"
"text/template"
)

// executes the template with the given data.
func execute(template *template.Template, data interface{}) []byte {
var buf bytes.Buffer

err := template.Execute(&buf, data)
if err != nil {
panic(err)
}

return buf.Bytes()
}
30 changes: 30 additions & 0 deletions internal/nginx/config/execute_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package config

import (
"testing"

"github.com/nginxinc/nginx-kubernetes-gateway/internal/nginx/config/http"
)

func TestExecute(t *testing.T) {
defer func() {
if r := recover(); r != nil {
t.Errorf("execute() panicked with %v", r)
}
}()

bytes := execute(serversTemplate, []http.Server{})
if len(bytes) == 0 {
t.Error("template.execute() did not generate anything")
}
}

func TestExecutePanics(t *testing.T) {
defer func() {
if r := recover(); r == nil {
t.Error("template.execute() did not panic")
}
}()

_ = execute(serversTemplate, "not-correct-data")
}
Loading