Skip to content

Commit cc9210d

Browse files
authored
Expand HTTPRoute Matching (#109)
Expand HTTPRoute Matching Adds an NJS module to support routing requests based on the HTTP method, headers, and or query parameters of the request. Currently, only processes one HTTPRouteMatch per route.
1 parent 9368a0d commit cc9210d

File tree

20 files changed

+1335
-46
lines changed

20 files changed

+1335
-46
lines changed

.github/workflows/ci.yml

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,27 @@ jobs:
6969
with:
7070
args: --timeout ${{ env.GOLANGCI_TIMEOUT }}
7171

72+
njs-lint:
73+
name: NJS Lint
74+
runs-on: ubuntu-20.04
75+
steps:
76+
- name: Checkout Repository
77+
uses: actions/checkout@v2
78+
- name: Run Prettier on NJS code
79+
id: prettier-run
80+
uses: rutajdash/[email protected]
81+
with:
82+
config_path: ${{ github.workspace }}/internal/nginx/modules/.prettierrc
83+
file_pattern: ${{ github.workspace }}/internal/nginx/modules/*.js
84+
prettier_version: 2.6.2
85+
- name: Prettier Output
86+
if: ${{ failure() }}
87+
shell: bash
88+
run: |
89+
echo "The following files are not formatted:"
90+
echo "${{steps.prettier-run.outputs.prettier_output}}"
91+
echo "Run \"make njs-fmt\" locally to format the code"
92+
7293
unit-tests:
7394
name: Unit Tests
7495
runs-on: ubuntu-20.04
@@ -89,6 +110,19 @@ jobs:
89110
path: ${{ github.workspace }}/cover.html
90111
if: always()
91112

113+
njs-unit-tests:
114+
name: NJS Unit Tests
115+
runs-on: ubuntu-20.04
116+
steps:
117+
- name: Checkout Repository
118+
uses: actions/checkout@v3
119+
- name: Setup Node.js Environment
120+
uses: actions/setup-node@v3
121+
with:
122+
node_version: 18
123+
- run: npm install mocha@^8.2 esm chai
124+
- run: npx mocha -r esm ${{ github.workspace }}/internal/nginx/modules/httpmatches_test.js
125+
92126
binary:
93127
name: Build Binary
94128
runs-on: ubuntu-20.04

Makefile

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,13 @@ delete-kind-cluster:
6565
fmt: ## Run go fmt against code.
6666
go fmt ./...
6767

68+
.PHONY: njs-fmt
69+
njs-fmt: ## Run prettier against the njs httpmatches module.
70+
docker run --rm \
71+
-v $(PWD)/internal/nginx/modules/:/njs-modules/ \
72+
node:18 \
73+
npx [email protected] --write njs-modules/ --config=njs-modules/.prettierrc
74+
6875
.PHONY: vet
6976
vet: ## Run go vet against code.
7077
go vet ./...
@@ -74,9 +81,15 @@ lint: ## Run golangci-lint against code.
7481
docker run --pull always --rm -v $(shell pwd):/nginx-kubernetes-gateway -w /nginx-kubernetes-gateway -v $(shell go env GOCACHE):/cache/go -e GOCACHE=/cache/go -e GOLANGCI_LINT_CACHE=/cache/go -v $(shell go env GOPATH)/pkg:/go/pkg golangci/golangci-lint:latest golangci-lint --color always run
7582

7683
.PHONY: unit-test
77-
unit-test:
84+
unit-test: ## Run unit tests for the go code
7885
go test ./... -race -coverprofile cover.out
7986
go tool cover -html=cover.out -o cover.html
8087

88+
njs-unit-test: ## Run unit tests for the njs httpmatches module.
89+
docker run --rm -w /src \
90+
-v $(PWD)/internal/nginx/modules/:/src/njs-modules/ \
91+
node:18 \
92+
/bin/bash -c "npm install mocha@^8.2 esm chai && npx mocha -r esm njs-modules/httpmatches_test.js"
93+
8194
.PHONY: dev-all
82-
dev-all: deps fmt vet lint unit-test
95+
dev-all: deps fmt njs-fmt vet lint unit-test njs-unit-test

README.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,18 @@ You can deploy NGINX Kubernetes Gateway on an existing Kubernetes 1.16+ cluster.
6262
kubectl apply -k "github.com/kubernetes-sigs/gateway-api/config/crd?ref=v0.4.2"
6363
```
6464

65+
1. Create the nginx-gateway namespace:
66+
67+
```
68+
kubectl apply -f deploy/manifests/namespace.yaml
69+
```
70+
71+
1. Create the njs-modules configmap:
72+
73+
```
74+
kubectl create configmap njs-modules --from-file=internal/nginx/modules/httpmatches.js -n nginx-gateway
75+
```
76+
6577
1. Deploy the NGINX Kubernetes Gateway:
6678
6779
Before deploying, make sure to update the Deployment spec in `nginx-gateway.yaml` to reference the image you built.

cmd/gateway/setup.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ func GatewayControllerParam(domain string, namespace string) ValidatorContext {
2424
return ValidatorContext{
2525
name,
2626
func(flagset *flag.FlagSet) error {
27-
// FIXME(yacobucci) this does not provide the same regex validation as
27+
// FIXME(f5yacobucci) this does not provide the same regex validation as
2828
// GatewayClass.ControllerName. provide equal and then specific validation
2929
param, err := flagset.GetString(name)
3030
if err != nil {

deploy/manifests/namespace.yaml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
apiVersion: v1
2+
kind: Namespace
3+
metadata:
4+
name: nginx-gateway

deploy/manifests/nginx-gateway.yaml

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,4 @@
11
apiVersion: v1
2-
kind: Namespace
3-
metadata:
4-
name: nginx-gateway
5-
---
6-
apiVersion: v1
72
kind: ServiceAccount
83
metadata:
94
name: nginx-gateway
@@ -77,10 +72,13 @@ spec:
7772
volumes:
7873
- name: nginx-config
7974
emptyDir: { }
75+
- name: njs-modules
76+
configMap:
77+
name: njs-modules
8078
initContainers:
8179
- image: busybox:1.34 # FIXME(pleshakov): use gateway container to init the Config with proper main config
8280
name: nginx-config-initializer
83-
command: [ 'sh', '-c', 'echo "events {} pid /etc/nginx/nginx.pid; http { include /etc/nginx/conf.d/*.conf; server { default_type text/html; return 404; } }" > /etc/nginx/nginx.conf && mkdir /etc/nginx/conf.d && chown 1001:0 /etc/nginx/conf.d' ]
81+
command: [ 'sh', '-c', 'echo "load_module /usr/lib/nginx/modules/ngx_http_js_module.so; events {} pid /etc/nginx/nginx.pid; http { include /etc/nginx/conf.d/*.conf; js_import /usr/lib/nginx/modules/njs/httpmatches.js; server { default_type text/html; return 404; } }" > /etc/nginx/nginx.conf && mkdir /etc/nginx/conf.d && chown 1001:0 /etc/nginx/conf.d' ]
8482
volumeMounts:
8583
- name: nginx-config
8684
mountPath: /etc/nginx
@@ -107,3 +105,5 @@ spec:
107105
volumeMounts:
108106
- name: nginx-config
109107
mountPath: /etc/nginx
108+
- name: njs-modules
109+
mountPath: /usr/lib/nginx/modules/njs
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
# Advanced Routing
2+
3+
In this example we will deploy NGINX Kubernetes Gateway and configure advanced routing rules for a simple cafe application.
4+
We will use `HTTPRoute` resources to route traffic to the cafe application based on a combination of the request method, headers, and query parameters.
5+
6+
## Running the Example
7+
8+
## 1. Deploy NGINX Kubernetes Gateway
9+
10+
1. Follow the [installation instructions](https://github.com/nginxinc/nginx-kubernetes-gateway/blob/main/README.md#run-nginx-gateway) to deploy NGINX Gateway.
11+
12+
1. Save the public IP address of NGINX Kubernetes Gateway into a shell variable:
13+
14+
```
15+
GW_IP=XXX.YYY.ZZZ.III
16+
```
17+
18+
1. Save the port of NGINX Kubernetes Gateway:
19+
20+
```
21+
GW_PORT=<port number>
22+
```
23+
24+
## 2. Deploy the Cafe Application
25+
26+
1. Create the coffee and the tea deployments and services:
27+
28+
```
29+
kubectl apply -f cafe.yaml
30+
```
31+
32+
1. Check that the Pods are running in the `default` namespace:
33+
34+
```
35+
kubectl -n default get pods
36+
NAME READY STATUS RESTARTS AGE
37+
coffee-6f4b79b975-2sb28 1/1 Running 0 12s
38+
tea-6fb46d899f-fm7zr 1/1 Running 0 12s
39+
```
40+
41+
## 3. Configure Routing
42+
43+
1. Create the `HTTPRoute` resources:
44+
45+
```
46+
kubectl apply -f cafe-routes.yaml
47+
```
48+
49+
## 4. Test the Application
50+
51+
We will use `curl` to send requests to the `coffee` and `tea` services.
52+
53+
### 4.1 Access coffee
54+
55+
Send a `POST` request to the path `/coffee` with the headers `X-Demo-Header:Demo-X1` and `version:v1`:
56+
57+
```
58+
curl --resolve cafe.example.com:$GW_PORT:$GW_IP http://cafe.example.com:$GW_PORT/coffee -X POST -H "X-Demo-Header:Demo-X1" -H "version:v1"
59+
Server address: 10.12.0.18:80
60+
Server name: coffee-7586895968-r26zn
61+
```
62+
63+
Header keys are case-insensitive, so we can also access coffee with the following request:
64+
65+
```
66+
curl --resolve cafe.example.com:$GW_PORT:$GW_IP http://cafe.example.com:$GW_PORT/coffee -X POST -H "X-DEMO-HEADER:Demo-X1" -H "Version:v1"
67+
Server address: 10.12.0.18:80
68+
Server name: coffee-7586895968-r26zn
69+
```
70+
71+
Only `POST` requests to the path `/coffee` with the headers `X-Demo-Header:Demo-X1` and `version:v1` will be able to access coffee.
72+
For example, try sending the following `GET` request:
73+
```
74+
curl --resolve cafe.example.com:$GW_PORT:$GW_IP http://cafe.example.com:$GW_PORT/coffee -H "X-Demo-Header:Demo-X1" -H "version:v1"
75+
```
76+
77+
NGINX Kubernetes Gateway returns a 405 since the request method does not match the method defined in the routing rule for `/coffee`.
78+
79+
### 4.2 Access tea
80+
81+
Send a request to the path `/tea` with the query parameter `Great=Example`:
82+
83+
```
84+
curl --resolve cafe.example.com:$GW_PORT:$GW_IP http://cafe.example.com:$GW_PORT/tea?Great=Example
85+
Server address: 10.12.0.19:80
86+
Server name: tea-7cd44fcb4d-xfw2x
87+
```
88+
89+
Query parameters are case-sensitive, so the case must match what you specify in the `HTTPRoute` resource.
90+
91+
Only requests to the path `/tea` with the query parameter `Great=Example` will be able to access tea.
92+
For example, try sending the following request:
93+
94+
```
95+
curl --resolve cafe.example.com:$GW_PORT:$GW_IP http://cafe.example.com:$GW_PORT/tea
96+
```
97+
98+
NGINX Kubernetes Gateway returns a 404 since the request does not satisfy the routing rule configured for `/tea`.
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
apiVersion: gateway.networking.k8s.io/v1alpha2
2+
kind: HTTPRoute
3+
metadata:
4+
name: cafe
5+
spec:
6+
hostnames:
7+
- "cafe.example.com"
8+
rules:
9+
- backendRefs:
10+
- name: main
11+
port: 80
12+
---
13+
apiVersion: gateway.networking.k8s.io/v1alpha2
14+
kind: HTTPRoute
15+
metadata:
16+
name: coffee
17+
spec:
18+
hostnames:
19+
- "cafe.example.com"
20+
rules:
21+
- matches:
22+
- path:
23+
type: PathPrefix
24+
value: /coffee
25+
method: POST
26+
headers:
27+
- name: X-Demo-Header # header names are case-insensitive
28+
value: Demo-X1 # header values are case-sensitive
29+
- name: version
30+
value: v1
31+
backendRefs:
32+
- name: coffee
33+
port: 80
34+
---
35+
apiVersion: gateway.networking.k8s.io/v1alpha2
36+
kind: HTTPRoute
37+
metadata:
38+
name: tea
39+
spec:
40+
hostnames:
41+
- "cafe.example.com"
42+
rules:
43+
- matches:
44+
- path:
45+
type: PathPrefix
46+
value: /tea
47+
queryParams:
48+
- name: Great # query params and values are case-sensitive
49+
value: Example
50+
backendRefs:
51+
- name: tea
52+
port: 80
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
apiVersion: apps/v1
2+
kind: Deployment
3+
metadata:
4+
name: coffee
5+
spec:
6+
replicas: 1
7+
selector:
8+
matchLabels:
9+
app: coffee
10+
template:
11+
metadata:
12+
labels:
13+
app: coffee
14+
spec:
15+
containers:
16+
- name: coffee
17+
image: nginxdemos/nginx-hello:plain-text
18+
ports:
19+
- containerPort: 8080
20+
---
21+
apiVersion: v1
22+
kind: Service
23+
metadata:
24+
name: coffee
25+
spec:
26+
ports:
27+
- port: 80
28+
targetPort: 8080
29+
protocol: TCP
30+
name: http
31+
selector:
32+
app: coffee
33+
---
34+
apiVersion: apps/v1
35+
kind: Deployment
36+
metadata:
37+
name: tea
38+
spec:
39+
replicas: 1
40+
selector:
41+
matchLabels:
42+
app: tea
43+
template:
44+
metadata:
45+
labels:
46+
app: tea
47+
spec:
48+
containers:
49+
- name: tea
50+
image: nginxdemos/nginx-hello:plain-text
51+
ports:
52+
- containerPort: 8080
53+
---
54+
apiVersion: v1
55+
kind: Service
56+
metadata:
57+
name: tea
58+
spec:
59+
ports:
60+
- port: 80
61+
targetPort: 8080
62+
protocol: TCP
63+
name: http
64+
selector:
65+
app: tea

examples/cafe-example/cafe.yaml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,6 @@ apiVersion: v1
5555
kind: Service
5656
metadata:
5757
name: tea
58-
labels:
5958
spec:
6059
ports:
6160
- port: 80

0 commit comments

Comments
 (0)