Skip to content

Commit 74832ba

Browse files
authored
Merge pull request #31 from cloudscale-ch/alain/udp
Add UDP Support
2 parents 26ce829 + 47b4042 commit 74832ba

File tree

6 files changed

+744
-50
lines changed

6 files changed

+744
-50
lines changed

examples/dns-dual-protocol.yaml

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
# Deploys a DNS server container and creates a loadbalancer service for it with both UDP and TCP:
2+
#
3+
# export KUBECONFIG=path/to/kubeconfig
4+
# kubectl apply -f dns-dual-protocol.yaml
5+
#
6+
# Wait for `kubectl describe service dns-server` to show "Loadbalancer Ensured",
7+
# then use the IP address found under "LoadBalancer Ingress" to connect to the
8+
# service.
9+
#
10+
# You can test the DNS service with dig:
11+
#
12+
# # Test UDP (default)
13+
# dig @$(kubectl get service dns-server -o jsonpath='{.status.loadBalancer.ingress[0].ip}') example.com
14+
#
15+
# # Test TCP explicitly
16+
# dig +tcp @$(kubectl get service dns-server -o jsonpath='{.status.loadBalancer.ingress[0].ip}') example.com
17+
#
18+
# To view the logs:
19+
#
20+
# kubectl logs -l "app=dns-server"
21+
#
22+
---
23+
apiVersion: v1
24+
kind: ConfigMap
25+
metadata:
26+
name: coredns-config
27+
data:
28+
Corefile: |
29+
.:53 {
30+
log
31+
errors
32+
health
33+
ready
34+
whoami
35+
forward . 8.8.8.8 9.9.9.9
36+
}
37+
---
38+
apiVersion: apps/v1
39+
kind: Deployment
40+
metadata:
41+
name: dns-server
42+
spec:
43+
replicas: 2
44+
selector:
45+
matchLabels:
46+
app: dns-server
47+
template:
48+
metadata:
49+
labels:
50+
app: dns-server
51+
spec:
52+
containers:
53+
- name: coredns
54+
image: coredns/coredns:1.11.1
55+
args:
56+
- -conf
57+
- /etc/coredns/Corefile
58+
volumeMounts:
59+
- name: config
60+
mountPath: /etc/coredns
61+
ports:
62+
- containerPort: 53
63+
protocol: UDP
64+
name: dns-udp
65+
- containerPort: 53
66+
protocol: TCP
67+
name: dns-tcp
68+
livenessProbe:
69+
httpGet:
70+
path: /health
71+
port: 8080
72+
readinessProbe:
73+
httpGet:
74+
path: /ready
75+
port: 8181
76+
volumes:
77+
- name: config
78+
configMap:
79+
name: coredns-config
80+
---
81+
apiVersion: v1
82+
kind: Service
83+
metadata:
84+
labels:
85+
app: dns-server
86+
name: dns-server
87+
spec:
88+
ports:
89+
- port: 53
90+
protocol: UDP
91+
targetPort: 53
92+
name: dns-udp
93+
- port: 53
94+
protocol: TCP
95+
targetPort: 53
96+
name: dns-tcp
97+
selector:
98+
app: dns-server
99+
type: LoadBalancer

examples/udp.yaml

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
# Deploys a UDP echo server container and creates a loadbalancer service for it:
2+
#
3+
# export KUBECONFIG=path/to/kubeconfig
4+
# kubectl apply -f udp-echo.yml
5+
#
6+
# Wait for `kubectl describe service udp-echo` to show "Loadbalancer Ensured",
7+
# then use the IP address found under "LoadBalancer Ingress" to connect to the
8+
# service.
9+
#
10+
# You can test the UDP service with netcat:
11+
#
12+
# echo "Tell me a joke" | nc -u -w 1 $(kubectl get service udp-echo -o jsonpath='{.status.loadBalancer.ingress[0].ip}') 5000
13+
#
14+
# To view the logs:
15+
#
16+
# kubectl logs -l "app=udp-echo"
17+
#
18+
---
19+
apiVersion: apps/v1
20+
kind: Deployment
21+
metadata:
22+
name: udp-echo
23+
spec:
24+
replicas: 2
25+
selector:
26+
matchLabels:
27+
app: udp-echo
28+
template:
29+
metadata:
30+
labels:
31+
app: udp-echo
32+
spec:
33+
containers:
34+
- name: udp-echo
35+
image: docker.io/alpine/socat
36+
command:
37+
- socat
38+
- "-v"
39+
- "UDP4-RECVFROM:5353,fork"
40+
- "SYSTEM:echo 'I could tell you a UDP joke, but you might not get it...',pipes"
41+
ports:
42+
- containerPort: 5353
43+
protocol: UDP
44+
---
45+
apiVersion: v1
46+
kind: Service
47+
metadata:
48+
annotations:
49+
k8s.cloudscale.ch/loadbalancer-health-monitor-type: udp-connect
50+
k8s.cloudscale.ch/loadbalancer-health-monitor-delay-s: "3"
51+
k8s.cloudscale.ch/loadbalancer-health-monitor-timeout-s: "2"
52+
labels:
53+
app: udp-echo
54+
name: udp-echo
55+
spec:
56+
ports:
57+
- port: 5000
58+
protocol: UDP
59+
targetPort: 5353
60+
name: udp
61+
selector:
62+
app: udp-echo
63+
type: LoadBalancer

pkg/cloudscale_ccm/loadbalancer.go

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -115,8 +115,14 @@ const (
115115
// as all pools have to be recreated.
116116
LoadBalancerPoolAlgorithm = "k8s.cloudscale.ch/loadbalancer-pool-algorithm"
117117

118-
// LoadBalancerPoolProtocol defines the protocol for all the pools of the
119-
// service. We are technically able to have different protocols for
118+
// LoadBalancerPoolProtocol defines the protocol the pools of a port
119+
// with protocol `TCP` use. Set it to `proxy` and `proxyv2` if TCP
120+
// traffic should be forwarded using these protocols.
121+
//
122+
// When setting the protocol of a port to `UDP`, traffic is always forwarded
123+
// using UDP.
124+
//
125+
// We are technically able to have different protocols for
120126
// different ports in a service, but as our options apart from `tcp` are
121127
// currently `proxy` and `proxyv2`, we go with Kubernetes's recommendation
122128
// to apply these protocols to all incoming connections the same way:
@@ -216,6 +222,8 @@ const (
216222
// LoadBalancerHealthMonitorType defines the approach the monitor takes.
217223
// (ping, tcp, http, https, tls-hello).
218224
//
225+
// Note that the same type is used for all ports in your service.
226+
//
219227
// See https://www.cloudscale.ch/en/api/v1#health-monitor-types
220228
//
221229
// Changing this annotation on an active service may lead to new
@@ -233,6 +241,9 @@ const (
233241
// LoadBalancerListenerProtocol defines the protocol used by the listening
234242
// port on the loadbalancer. Currently, only tcp is supported.
235243
//
244+
// This property is ignored for ports with the protocol `UDP`, where
245+
// we always create udp listeners.
246+
//
236247
// See https://www.cloudscale.ch/en/api/v1#listener-protocols
237248
//
238249
// Changing this annotation on an established service may cause downtime

pkg/cloudscale_ccm/reconcile.go

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -122,9 +122,10 @@ func desiredLbState(
122122

123123
for _, port := range serviceInfo.Service.Spec.Ports {
124124

125-
if port.Protocol != "TCP" {
125+
if port.Protocol != v1.ProtocolTCP && port.Protocol != v1.ProtocolUDP {
126126
return nil, fmt.Errorf(
127-
"service %s: cannot use %s for %d, only TCP is supported",
127+
"service %s: cannot use %s for %d"+
128+
", only TCP and UDP are supported",
128129
serviceInfo.Service.Name,
129130
port.Protocol,
130131
port.Port)
@@ -145,10 +146,15 @@ func desiredLbState(
145146
}
146147
}
147148

149+
poolProtocol := protocol
150+
if port.Protocol != v1.ProtocolTCP {
151+
poolProtocol = "udp"
152+
}
153+
148154
pool := cloudscale.LoadBalancerPool{
149155
Name: poolName(port.Protocol, port.Name),
150156
Algorithm: algorithm,
151-
Protocol: protocol,
157+
Protocol: poolProtocol,
152158
}
153159
s.pools = append(s.pools, &pool)
154160

@@ -213,7 +219,7 @@ func desiredLbState(
213219
s.monitors[&pool] = append(s.monitors[&pool], *monitor)
214220

215221
// Add a listener for each pool
216-
listener, err := listenerForPort(serviceInfo, int(port.Port))
222+
listener, err := listenerForPort(serviceInfo, port)
217223
if err != nil {
218224
return nil, err
219225
}
@@ -813,16 +819,21 @@ func runActions(
813819
// annotations into consideration.
814820
func listenerForPort(
815821
serviceInfo *serviceInfo,
816-
port int,
822+
port v1.ServicePort,
817823
) (*cloudscale.LoadBalancerListener, error) {
818824

819825
var (
820826
listener = cloudscale.LoadBalancerListener{}
821827
err error
822828
)
823829

824-
listener.Protocol = serviceInfo.annotation(LoadBalancerListenerProtocol)
825-
listener.ProtocolPort = port
830+
listenerProtocol := serviceInfo.annotation(LoadBalancerListenerProtocol)
831+
if port.Protocol != v1.ProtocolTCP {
832+
listenerProtocol = "udp"
833+
}
834+
835+
listener.Protocol = listenerProtocol
836+
listener.ProtocolPort = int(port.Port)
826837
listener.Name = listenerName(listener.Protocol, listener.ProtocolPort)
827838

828839
listener.TimeoutClientDataMS, err = serviceInfo.annotationInt(

0 commit comments

Comments
 (0)