Skip to content

Commit 992e13a

Browse files
committed
Move TFTP server to its own pod on server and proxy
Running and accessing a TFTP server on Kubernetes requires either a load balancer or setting the pod to use hostNetwork. In order to minimize the side effects of the hostNetwork to only the TFTP service, the TFTP server needs to move to its own pod on both the server and the proxy. Note that not all load balancers will work with TFTP: serviceLB doesn't while MetalLB does. The proxy server can also work for the server with adjusted parameters to the command. This allows the TFTP pod of the server to not have to share a volume with the server pod. This way it can be scheduled on different nodes later on if required.
1 parent 000024f commit 992e13a

39 files changed

+374
-214
lines changed

containers/proxy-helm/README.md

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ See the [requirements documentation](https://www.uyuni-project.org/uyuni-docs/en
2828

2929
### Exposing ports
3030

31-
Uyuni proxy requires some TCP and UDP ports to be routed to its services.
31+
Uyuni proxy requires some TCP ports to be routed to its services.
3232
Here is a list of the ports to map:
3333

3434

@@ -37,9 +37,12 @@ Here is a list of the ports to map:
3737
| TCP | 8022 | ssh | 8022 |
3838
| TCP | 4505 | salt | 4505 |
3939
| TCP | 4506 | salt | 4506 |
40-
| UDP | 69 | tftp | 69 |
4140

4241

42+
Exposing the `tftp` service has to be done differently due to the way TFTP protocol is working.
43+
Either use the host network using the `tftp.hostnetwork` value or configure a load balancer for the `tftp` service.
44+
Note that not all load balancers will work: `serviceLB` implementation is not compatible with TFTP protocol, while MetalLB works.
45+
4346
## Usage
4447

4548
Once installed, systems can be connected the to proxy.

containers/proxy-helm/templates/deployment.yaml

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -106,17 +106,6 @@ spec:
106106
readOnly: true
107107
ports:
108108
- containerPort: 22
109-
110-
- name: tftpd
111-
image: {{ include "uyuni.image" (dict "name" "proxy-tftpd" "global" . "local" .Values.tftpd) }}
112-
imagePullPolicy: "{{ .Values.pullPolicy }}"
113-
volumeMounts:
114-
- name: tftpd-volume
115-
mountPath: /etc/uyuni/
116-
readOnly: true
117-
ports:
118-
- containerPort: 69
119-
protocol: UDP
120109
volumes:
121110
- name: httpd-volume
122111
projected:

containers/proxy-helm/templates/k3s-ingress-routes.yaml

Lines changed: 0 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -55,23 +55,5 @@ spec:
5555
services:
5656
- name: salt
5757
port: 4506
58-
---
59-
apiVersion: traefik.io/v1alpha1
60-
kind: IngressRouteUDP
61-
metadata:
62-
name: tftp-router
63-
namespace: "{{ .Release.Namespace }}"
64-
labels:
65-
app.kubernetes.io/component: proxy
66-
app.kubernetes.io/part-of: uyuni
67-
annotations:
68-
kubernetes.io/ingress.class: traefik
69-
spec:
70-
entryPoints:
71-
- tftp
72-
routes:
73-
- services:
74-
- name: tftp
75-
port: 69
7658
{{- end }}
7759

containers/proxy-helm/templates/services.yaml

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@ spec:
8080
{{- if eq .Values.services.type "NodePort" }}
8181
nodePort: {{ .Values.ssh.ports.ssh }}
8282
{{- end }}
83+
{{- if not .Values.tftp.hostNetwork }}
8384
---
8485
apiVersion: v1
8586
kind: Service
@@ -97,11 +98,14 @@ spec:
9798
selector:
9899
app.kubernetes.io/component: proxy
99100
app.kubernetes.io/part-of: uyuni
100-
type: {{ .Values.services.type }}
101+
# ClusterIP or NodePort wouldn't work for TFTP if not using the host network
102+
type: LoadBalancer
103+
# Ensures all packets from a single client go to the same pod
104+
sessionAffinity: ClientIP
105+
# Preserves the client's source IP and avoids extra hops between nodes
106+
externalTrafficPolicy: Local
101107
ports:
102108
- name: tftp
103109
port: 69
104110
protocol: UDP
105-
{{- if eq .Values.services.type "NodePort" }}
106-
nodePort: {{ .Values.tftpd.ports.tftp }}
107111
{{- end }}
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
apiVersion: apps/v1
2+
kind: Deployment
3+
metadata:
4+
name: uyuni-proxy-tftp
5+
namespace: "{{ .Release.Namespace }}"
6+
labels:
7+
app.kubernetes.io/component: proxy-tftp
8+
app.kubernetes.io/part-of: uyuni
9+
spec:
10+
replicas: 1
11+
selector:
12+
matchLabels:
13+
app.kubernetes.io/component: proxy-tftp
14+
app.kubernetes.io/part-of: uyuni
15+
template:
16+
metadata:
17+
labels:
18+
app.kubernetes.io/component: proxy-tftp
19+
app.kubernetes.io/part-of: uyuni
20+
spec:
21+
{{- if .Values.registrySecret }}
22+
imagePullSecrets:
23+
- name: {{ .Values.registrySecret }}
24+
{{- end }}
25+
{{- if .Values.tftp.hostNetwork }}
26+
dnsPolicy: ClusterFirstWithHostNet
27+
hostNetwork: true
28+
{{- end }}
29+
containers:
30+
- name: tftpd
31+
image: {{ include "uyuni.image" (dict "name" "shared-tftpd" "global" . "local" .Values.tftp) }}
32+
imagePullPolicy: "{{ .Values.pullPolicy }}"
33+
command:
34+
- /usr/bin/tftp_wrapper.py
35+
- --httpHost
36+
- web.{{ .Release.Namespace }}.svc
37+
volumeMounts:
38+
- name: tftpd-volume
39+
mountPath: /etc/uyuni/
40+
readOnly: true
41+
ports:
42+
- containerPort: 69
43+
protocol: UDP
44+
volumes:
45+
- name: tftpd-volume
46+
projected:
47+
sources:
48+
- configMap:
49+
name: proxy-configmap
50+
items:
51+
- key: config.yaml
52+
path: config.yaml
53+
- configMap:
54+
name: uyuni-ca
55+
items:
56+
- key: ca.crt
57+
path: ca.crt
58+
mode: 420

containers/proxy-helm/tests/deployment_test.yaml

Lines changed: 0 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -66,13 +66,6 @@ tests:
6666
name: ssh
6767
image: registry.opensuse.org/uyuni/proxy-ssh:latest
6868
imagePullPolicy: Always
69-
- contains:
70-
path: spec.template.spec.containers
71-
any: true
72-
content:
73-
name: tftpd
74-
image: registry.opensuse.org/uyuni/proxy-tftpd:latest
75-
imagePullPolicy: Always
7669

7770
# Verify registrySecret is not present by default
7871
- notExists:
@@ -134,13 +127,6 @@ tests:
134127
image: my.custom.registry/uyuni/proxy-ssh:5.2.3
135128
imagePullPolicy: IfNotPresent
136129
any: true
137-
- contains:
138-
path: spec.template.spec.containers
139-
content:
140-
name: tftpd
141-
image: my.custom.registry/uyuni/proxy-tftpd:5.2.3
142-
imagePullPolicy: IfNotPresent
143-
any: true
144130

145131
- it: should use component-specific overrides for all containers
146132
set:
@@ -156,7 +142,6 @@ tests:
156142
salt: { image: "custom/salt", tag: "v2" }
157143
squid: { image: "custom/squid", tag: "v3" }
158144
ssh: { image: "custom/ssh", tag: "v4" }
159-
tftpd: { image: "custom/tftp", tag: "v5" }
160145
asserts:
161146
# Check that local image + local tag overrides work for all
162147
- contains:
@@ -183,10 +168,6 @@ tests:
183168
path: spec.template.spec.containers
184169
content: { name: ssh, image: "custom/ssh:v4" }
185170
any: true
186-
- contains:
187-
path: spec.template.spec.containers
188-
content: { name: tftpd, image: "custom/tftp:v5" }
189-
any: true
190171

191172
- it: should include imagePullSecrets when registrySecret is provided
192173
set:

containers/proxy-helm/tests/services_test.yaml

Lines changed: 24 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ suite: test global values parsing
22
templates:
33
- templates/services.yaml
44
tests:
5-
- it: should create services with ClusterIP by default
5+
- it: should create services with ClusterIP by default except for tftp
66
set:
77
global.config: |
88
max_cache_size_mb: 2048
@@ -155,7 +155,7 @@ tests:
155155

156156
- equal:
157157
path: spec.type
158-
value: ClusterIP
158+
value: LoadBalancer
159159
documentSelector:
160160
path: metadata.name
161161
value: tftp
@@ -275,19 +275,7 @@ tests:
275275
# tftp service checks
276276
- equal:
277277
path: spec.type
278-
value: NodePort
279-
documentSelector:
280-
path: metadata.name
281-
value: tftp
282-
283-
- contains:
284-
path: spec.ports
285-
content:
286-
name: tftp
287-
port: 69
288-
nodePort: 30069
289-
protocol: UDP
290-
any: true
278+
value: LoadBalancer
291279
documentSelector:
292280
path: metadata.name
293281
value: tftp
@@ -398,26 +386,6 @@ tests:
398386
path: metadata.name
399387
value: ssh
400388

401-
# tftp service checks
402-
- equal:
403-
path: spec.type
404-
value: NodePort
405-
documentSelector:
406-
path: metadata.name
407-
value: tftp
408-
409-
- contains:
410-
path: spec.ports
411-
content:
412-
name: tftp
413-
port: 69
414-
nodePort: 10069
415-
protocol: UDP
416-
any: true
417-
documentSelector:
418-
path: metadata.name
419-
value: tftp
420-
421389
- it: should create services with custom annotations
422390
set:
423391
global.config: |
@@ -472,3 +440,24 @@ tests:
472440
documentSelector:
473441
path: metadata.name
474442
value: tftp
443+
444+
- it: should not create TFTP service with hostNetwork
445+
set:
446+
global.config: |
447+
max_cache_size_mb: 2048
448+
server: parent.example.org
449+
proxy_fqdn: proxy.example.org
450+
email: foo@example.org
451+
log_level: 2
452+
global.httpd: "httpd_content: test httpd"
453+
global.ssh: "ssh_content: test ssh"
454+
tftp:
455+
hostNetwork: true
456+
asserts:
457+
# tftp service checks
458+
- hasDocuments:
459+
count: 0
460+
documentSelector:
461+
path: metadata.name
462+
value: tftp
463+
skipEmptyTemplates: true
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
suite: test TFTP deployment
2+
templates:
3+
- templates/tftp.yaml
4+
tests:
5+
- it: should create the tftp deployment with default image
6+
set:
7+
global.config: |
8+
max_cache_size_mb: 2048
9+
server: parent.example.org
10+
proxy_fqdn: proxy.example.org
11+
email: foo@example.org
12+
log_level: 2
13+
global.httpd: "httpd_content: test httpd"
14+
global.ssh: "ssh_content: test ssh"
15+
release:
16+
namespace: proxy1
17+
asserts:
18+
- containsDocument:
19+
kind: Deployment
20+
apiVersion: apps/v1
21+
name: uyuni-proxy-tftp
22+
namespace: proxy1
23+
24+
- contains:
25+
path: spec.template.spec.containers
26+
any: true
27+
content:
28+
name: tftpd
29+
image: registry.opensuse.org/uyuni/shared-tftpd:latest
30+
imagePullPolicy: Always
31+
command:
32+
- /usr/bin/tftp_wrapper.py
33+
- --httpHost
34+
- web.proxy1.svc
35+
36+
- it: should use top-level repository, tag and pullPolicy
37+
set:
38+
global.config: |
39+
max_cache_size_mb: 2048
40+
server: parent.example.org
41+
proxy_fqdn: proxy.example.org
42+
email: foo@example.org
43+
log_level: 2
44+
global.httpd: "httpd_content: test httpd"
45+
global.ssh: "ssh_content: test ssh"
46+
repository: my.custom.registry/uyuni
47+
tag: 5.2.3
48+
pullPolicy: IfNotPresent
49+
asserts:
50+
- contains:
51+
path: spec.template.spec.containers
52+
content:
53+
name: tftpd
54+
image: my.custom.registry/uyuni/shared-tftpd:5.2.3
55+
imagePullPolicy: IfNotPresent
56+
any: true
57+
58+
- it: should use component-specific overrides
59+
set:
60+
global.config: |
61+
max_cache_size_mb: 2048
62+
server: parent.example.org
63+
proxy_fqdn: proxy.example.org
64+
email: foo@example.org
65+
log_level: 2
66+
global.httpd: "httpd_content: test httpd"
67+
global.ssh: "ssh_content: test ssh"
68+
tftp: { image: "custom/tftp", tag: "v5" }
69+
asserts:
70+
- contains:
71+
path: spec.template.spec.containers
72+
content: { name: tftpd, image: "custom/tftp:v5" }
73+
any: true
74+
75+
- it: should include imagePullSecrets when registrySecret is provided
76+
set:
77+
global.config: |
78+
max_cache_size_mb: 2048
79+
server: parent.example.org
80+
proxy_fqdn: proxy.example.org
81+
email: foo@example.org
82+
log_level: 2
83+
global.httpd: "httpd_content: test httpd"
84+
global.ssh: "ssh_content: test ssh"
85+
registrySecret: "my-registry-key"
86+
asserts:
87+
- equal:
88+
path: spec.template.spec.imagePullSecrets[0].name
89+
value: "my-registry-key"
90+
91+
- it: should set hostNetwork when requested
92+
set:
93+
global.config: |
94+
max_cache_size_mb: 2048
95+
server: parent.example.org
96+
proxy_fqdn: proxy.example.org
97+
email: foo@example.org
98+
log_level: 2
99+
global.httpd: "httpd_content: test httpd"
100+
global.ssh: "ssh_content: test ssh"
101+
tftp:
102+
hostNetwork: true
103+
asserts:
104+
- equal:
105+
path: spec.template.spec.hostNetwork
106+
value: true
107+
108+
- equal:
109+
path: spec.template.spec.dnsPolicy
110+
value: ClusterFirstWithHostNet

0 commit comments

Comments
 (0)