Skip to content

Commit 0c443c8

Browse files
committed
Add TCP/UDP load balancing example
1 parent ef84a56 commit 0c443c8

File tree

6 files changed

+332
-2
lines changed

6 files changed

+332
-2
lines changed

docs/nginx-ingress-controllers.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ The table below summarizes the key difference between nginxinc/kubernetes-ingres
2020
| Merging Ingress rules with the same host | Supported | Supported | Supported |
2121
| HTTP load balancing extensions - Annotations | See the [supported annotations](https://github.com/kubernetes/ingress-nginx/blob/master/docs/user-guide/annotations.md) | See the [supported annotations](https://github.com/nginxinc/kubernetes-ingress/tree/master/examples/customization) | See the [supported annotations](https://github.com/nginxinc/kubernetes-ingress/tree/master/examples/customization)|
2222
| HTTP load balancing extensions -- ConfigMap | See the [supported ConfigMap keys](https://github.com/kubernetes/ingress-nginx/blob/master/docs/user-guide/configmap.md) | See the [supported ConfigMap keys](https://github.com/nginxinc/kubernetes-ingress/tree/master/examples/customization) | See the [supported ConfigMap keys](https://github.com/nginxinc/kubernetes-ingress/tree/master/examples/customization) |
23-
| TCP/UDP | Supported via a ConfigMap | Not supported | Not supported |
23+
| TCP/UDP | Supported via a ConfigMap | Supported via a ConfigMap with native NGINX configuration | Supported via a ConfigMap with native NGINX configuration |
2424
| Websocket | Supported | Supported via an [annotation](https://github.com/nginxinc/kubernetes-ingress/tree/master/examples/websocket) | Supported via an [annotation](https://github.com/nginxinc/kubernetes-ingress/tree/master/examples/websocket) |
2525
| TCP SSL Passthrough | Supported via a ConfigMap | Not supported | Not supported |
2626
| JWT validation | Not supported | Not supported | Supported |

examples/customization/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ The table below summarizes all of the options. For some of them, there are examp
6767
| `nginx.com/health-checks-mandatory-queue` | N/A | When active health checks are mandatory, configures a queue for temporary storing incoming requests during the time when NGINX Plus is checking the health of the endpoints after a configuration reload. | `0` | [Support for Active Health Checks](../health-checks). |
6868
| `nginx.com/slow-start` | N/A | Sets the upstream server [slow-start period](https://docs.nginx.com/nginx/admin-guide/load-balancer/http-load-balancer/#server-slow-start). By default, slow-start is activated after a server becomes [available](https://docs.nginx.com/nginx/admin-guide/load-balancer/http-health-check/#passive-health-checks) or [healthy](https://docs.nginx.com/nginx/admin-guide/load-balancer/http-health-check/#active-health-checks). To enable slow-start for newly added servers, configure [mandatory active health checks](../health-checks). | `"0s"` | |
6969
| N/A | `external-status-address` | Sets the address to be reported in the status of Ingress resources. Requires the `-report-status` command-line argument. Overrides the `-external-service` argument. | N/A | [Report Ingress Status](../../docs/report-ingress-status.md). |
70-
| N/A | `stream-snippets` | Sets a custom snippet in stream context. | N/A | |
70+
| N/A | `stream-snippets` | Sets a custom snippet in stream context. | N/A | [Support for TCP/UDP Load Balancing](../tcp-udp). |
7171
| N/A | `stream-log-format` | Sets the custom [log format](http://nginx.org/en/docs/stream/ngx_stream_log_module.html#log_format) for TCP/UDP load balancing. | See the [template file](../../nginx-controller/nginx/nginx.conf.tmpl). | |
7272

7373
## Using ConfigMaps

examples/tcp-udp/README.md

Lines changed: 188 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,188 @@
1+
# Support for TCP/UDP Load Balancing
2+
3+
In this example we deploy the NGINX or NGINX Plus Ingress controller, a DNS server and then configure both TCP and UDP load balancing for the DNS server using the `stream-snippets` [ConfigMap key](../customization).
4+
5+
The standard Kubernetes Ingress resources assume that all traffic is HTTP-based; they do not cater for the case of basic TCP or UDP load balancing. In this example, we use the `stream-snippets` ConfigMap key to embed the required TCP and UDP load-balancing configuration directly into the `stream{}` block of the NGINX configuration file.
6+
7+
With NGINX, we’ll use the DNS name or virtual IP address to identify the service, and rely on kube-proxy to perform the internal load-balancing across the pool of pods. With NGINX Plus, we can use a [headless](https://kubernetes.io/docs/concepts/services-networking/service/#headless-services) service and its DNS name to obtain the real IP addresses of the pods behind the service, and load-balance across these. NGINX Plus re-resolves the DNS name frequently, so will update automatically when new pods are deployed or removed.
8+
9+
## Prerequisites
10+
11+
* We use `dig` for testing. Make sure it is installed on your machine.
12+
* We use native NGINX configuration to configure TCP/UDP load balancing. If you'd like to better understand the example configuration, read about [TCP/UDP load balancing](https://docs.nginx.com/nginx/admin-guide/load-balancer/tcp-udp-load-balancer/) and [DNS service discovery](https://www.nginx.com/blog/dns-service-discovery-nginx-plus/) in NGINX/NGINX Plus.
13+
14+
## Running the Example
15+
16+
### 1. Deploy the Ingress Controller
17+
18+
1. Follow the installation [instructions](../../docs/installation.md) to deploy the Ingress controller. Make sure to expose port 5353 of the Ingress controller
19+
both for TCP and UDP traffic.
20+
21+
2. Save the public IP address of the Ingress controller into a shell variable:
22+
```
23+
$ IC_IP=XXX.YYY.ZZZ.III
24+
```
25+
**Note**: If you'd like to expose the Ingress controller via a service with the type LoadBalancer, it is not allowed to create a type LoadBalancer service for both TCP and UDP protocols. To overcome this limitation, create two separate services, one for TCP and the other for UDP. In this case you will end up with two separate public IPs, one for TCP and the other for UDP. Use the former in Step 4.2 and the latter in Step 4.1.
26+
3. Save port 5353 of the Ingress controller into a shell variable:
27+
```
28+
$ IC_5353_PORT=<port number>
29+
```
30+
31+
### 2. Deploy the DNS Server
32+
33+
We deploy two replicas of [CoreDNS](https://coredns.io/), configured to forward DNS queries to `8.8.8.8`. We also create two services for CoreDNS pods -- `coredns` and `coredns-headless`. The reason for that is explained in Steps 3.1 and 3.2.
34+
35+
Deploy the DNS server:
36+
37+
```
38+
$ kubectl apply -f dns.yaml
39+
```
40+
41+
### 3. Configure Load Balancing
42+
43+
We use `stream-snippets` ConfigMap key to configure TCP and UDP load balancing for our CoreDNS pods.
44+
45+
1. Create load balancing configuration. In our example we create one server that listens for TCP traffic on port 5353 and one server that listens for UDP traffic on the same port. Both servers load balance the incoming traffic to our CoreDNS pods:
46+
47+
* For NGINX, we use the following configuration:
48+
```nginx
49+
upstream coredns-udp {
50+
server coredns.default.svc.cluster.local:53;
51+
}
52+
53+
server {
54+
listen 5353 udp;
55+
proxy_pass coredns-udp;
56+
proxy_responses 1;
57+
}
58+
59+
upstream coredns-tcp {
60+
server coredns.default.svc.cluster.local:53;
61+
}
62+
63+
server {
64+
listen 5353;
65+
proxy_pass coredns-tcp;
66+
}
67+
```
68+
69+
We define upstream servers using a DNS name. When NGINX is reloaded, the DNS name will be resolved into the virtual IP of the `coredns` service.
70+
71+
**Note**: NGINX will fail to reload if the DNS name `coredns.default.svc.cluster.local` cannot be resolved. To avoid that, you can define the upstream servers using the virtual IP of the `coredns` service instead of the DNS name.
72+
73+
* For NGINX Plus, we use a different configuration:
74+
```nginx
75+
resolver kube-dns.kube-system.svc.cluster.local valid=5s;
76+
77+
upstream coredns-udp {
78+
zone coredns-udp 64k;
79+
server coredns-headless.default.svc.cluster.local service=_dns._udp resolve;
80+
}
81+
82+
server {
83+
listen 5353 udp;
84+
proxy_pass coredns-udp;
85+
proxy_responses 1;
86+
status_zone coredns-udp;
87+
}
88+
89+
upstream coredns-tcp {
90+
zone coredns-tcp 64k;
91+
server coredns-headless.default.svc.cluster.local service=_dns-tcp._tcp resolve;
92+
}
93+
94+
server {
95+
listen 5353;
96+
proxy_pass coredns-tcp;
97+
status_zone coredns-tcp;
98+
}
99+
```
100+
NGINX Plus supports re-resolving DNS names with the `resolve` parameter of the `upstream` directive, which we take an advantage of in our example. Additionally, when the `resolve` parameter is used, NGINX Plus will not fail to reload if the name of an upstream cannot be resolved, in contrast with NGINX. In addition to IP addresses, NGINX Plus will discover ports through DNS SRV records.
101+
102+
To resolve IP addresses and ports, NGINX Plus uses the Kube-DNS, defined with the `resolver` directive. We also set the `valid` parameter to `5s` to make NGINX Plus re-resolve DNS names every 5s.
103+
104+
Instead of `coredns` service, we use `coredns-headless` service. This service is created as a [headless service](https://kubernetes.io/docs/concepts/services-networking/service/#headless-services), meaning that no virtual IP is allocated for that service and NGINX Plus will be able to resolve the IP addresses of all the CoreDNS pods.
105+
106+
**Note**: NGINX Plus will fail to reload if the DNS name, specified in the `resolver` directive, cannot be resolved. To avoid that, you can define the resolver using the virtual IP of the `kube-dns` service instead of the DNS name.
107+
108+
1. Update the ConfigMap with the `stream-snippets` containing the load balancing configuration:
109+
* For NGINX, run:
110+
```
111+
$ kubectl apply -f nginx-config.yaml
112+
```
113+
* For NGINX Plus, run:
114+
```
115+
$ kubectl apply -f nginx-plus-config.yaml
116+
```
117+
1. Make sure NGINX or NGINX Plus is successfully reloaded:
118+
```
119+
$ kubectl describe configmap nginx-config -n nginx-ingress
120+
. . .
121+
Events:
122+
Type Reason Age From Message
123+
---- ------ ---- ---- -------
124+
Normal Updated 3s nginx-ingress-controller Configuration from nginx-ingress/nginx-config was updated
125+
```
126+
127+
128+
### 4. Test the DNS Server
129+
130+
To test that the configured TCP/UDP load balancing works, we resolve the name `kubernetes.io` using our DNS server available through the Ingress Controller:
131+
132+
1. Resolve `kubernetes.io` through UDP:
133+
```
134+
$ dig @$IC_IP -p $IC_5353_PORT kubernetes.io
135+
136+
; <<>> DiG 9.10.6 <<>> @<REDACTED>> -p 5353 kubernetes.io
137+
; (1 server found)
138+
;; global options: +cmd
139+
;; Got answer:
140+
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 33368
141+
;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1
142+
143+
;; OPT PSEUDOSECTION:
144+
; EDNS: version: 0, flags:; udp: 512
145+
;; QUESTION SECTION:
146+
;kubernetes.io. IN A
147+
148+
;; ANSWER SECTION:
149+
kubernetes.io. 299 IN A 45.54.44.100
150+
151+
;; Query time: 111 msec
152+
;; SERVER:<REDACTED>#5353(<REDACTED>)
153+
;; WHEN: Fri Aug 17 12:49:54 BST 2018
154+
;; MSG SIZE rcvd: 71
155+
```
156+
157+
1. Resolve `kubernetes.io` through TCP:
158+
```
159+
$ dig @$IC_IP -p $IC_5353_PORT kubernetes.io +tcp
160+
161+
; <<>> DiG 9.10.6 <<>> @<REDACTED> -p 5353 kubernetes.io +tcp
162+
; (1 server found)
163+
;; global options: +cmd
164+
;; Got answer:
165+
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 49032
166+
;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1
167+
168+
;; OPT PSEUDOSECTION:
169+
; EDNS: version: 0, flags:; udp: 512
170+
;; QUESTION SECTION:
171+
;kubernetes.io. IN A
172+
173+
;; ANSWER SECTION:
174+
kubernetes.io. 146 IN A 45.54.44.100
175+
176+
;; Query time: 95 msec
177+
;; SERVER: <REDACTED>#5353(<REDACTED>)
178+
;; WHEN: Fri Aug 17 12:52:25 BST 2018
179+
;; MSG SIZE rcvd: 71
180+
```
181+
1. Look at the Ingress Controller logs:
182+
```
183+
$ kubectl logs <nginx-ingress-pod> -n nginx-ingress
184+
. . .
185+
<REDACTED> [17/Aug/2018:11:49:54 +0000] UDP 200 71 42 0.016
186+
<REDACTED> [17/Aug/2018:11:52:25 +0000] TCP 200 73 44 0.098
187+
```
188+

examples/tcp-udp/dns.yaml

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
apiVersion: v1
2+
kind: ConfigMap
3+
metadata:
4+
name: coredns
5+
data:
6+
Corefile: |
7+
.:53 {
8+
forward . 8.8.8.8:53
9+
log
10+
}
11+
---
12+
apiVersion: extensions/v1beta1
13+
kind: Deployment
14+
metadata:
15+
name: coredns
16+
spec:
17+
replicas: 2
18+
selector:
19+
matchLabels:
20+
app: coredns
21+
template:
22+
metadata:
23+
labels:
24+
app: coredns
25+
spec:
26+
containers:
27+
- name: coredns
28+
image: coredns/coredns:1.2.0
29+
args: [ "-conf", "/etc/coredns/Corefile" ]
30+
volumeMounts:
31+
- name: config-volume
32+
mountPath: /etc/coredns
33+
readOnly: true
34+
ports:
35+
- containerPort: 53
36+
name: dns
37+
protocol: UDP
38+
- containerPort: 53
39+
name: dns-tcp
40+
protocol: TCP
41+
securityContext:
42+
allowPrivilegeEscalation: false
43+
capabilities:
44+
add:
45+
- NET_BIND_SERVICE
46+
drop:
47+
- all
48+
readOnlyRootFilesystem: true
49+
volumes:
50+
- name: config-volume
51+
configMap:
52+
name: coredns
53+
items:
54+
- key: Corefile
55+
path: Corefile
56+
---
57+
apiVersion: v1
58+
kind: Service
59+
metadata:
60+
name: coredns
61+
spec:
62+
selector:
63+
app: coredns
64+
ports:
65+
- name: dns
66+
port: 53
67+
protocol: UDP
68+
- name: dns-tcp
69+
port: 53
70+
protocol: TCP
71+
---
72+
apiVersion: v1
73+
kind: Service
74+
metadata:
75+
name: coredns-headless
76+
spec:
77+
clusterIP: None
78+
selector:
79+
app: coredns
80+
ports:
81+
- name: dns
82+
port: 53
83+
protocol: UDP
84+
- name: dns-tcp
85+
port: 53
86+
protocol: TCP

examples/tcp-udp/nginx-config.yaml

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
kind: ConfigMap
2+
apiVersion: v1
3+
metadata:
4+
name: nginx-config
5+
namespace: nginx-ingress
6+
data:
7+
stream-snippets: |
8+
upstream coredns-udp {
9+
server coredns.default.svc.cluster.local:53;
10+
}
11+
12+
server {
13+
listen 5353 udp;
14+
proxy_pass coredns-udp;
15+
proxy_responses 1;
16+
}
17+
18+
upstream coredns-tcp {
19+
server coredns.default.svc.cluster.local:53;
20+
}
21+
22+
server {
23+
listen 5353;
24+
proxy_pass coredns-tcp;
25+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
kind: ConfigMap
2+
apiVersion: v1
3+
metadata:
4+
name: nginx-config
5+
namespace: nginx-ingress
6+
data:
7+
stream-snippets: |
8+
resolver kube-dns.kube-system.svc.cluster.local valid=5s;
9+
10+
upstream coredns-udp {
11+
zone coredns-udp 64k;
12+
server coredns-headless.default.svc.cluster.local service=_dns._udp resolve;
13+
}
14+
15+
server {
16+
listen 5353 udp;
17+
proxy_pass coredns-udp;
18+
proxy_responses 1;
19+
status_zone coredns-udp;
20+
}
21+
22+
upstream coredns-tcp {
23+
zone coredns-tcp 64k;
24+
server coredns-headless.default.svc.cluster.local service=_dns-tcp._tcp resolve;
25+
}
26+
27+
server {
28+
listen 5353;
29+
proxy_pass coredns-tcp;
30+
status_zone coredns-tcp;
31+
}

0 commit comments

Comments
 (0)