Skip to content

Commit ac4523e

Browse files
authored
Merge pull request #4564 from kubernetes-sigs/add-new-topology-spread-constraint
charts: add ability to specify topologySpreadConstraints
2 parents 61c1d2d + e6c2551 commit ac4523e

File tree

7 files changed

+628
-0
lines changed

7 files changed

+628
-0
lines changed

charts/headlamp/README.md

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -254,6 +254,7 @@ httpRoute:
254254
| nodeSelector | object | `{}` | Node labels for pod assignment |
255255
| tolerations | list | `[]` | Pod tolerations |
256256
| affinity | object | `{}` | Pod affinity settings |
257+
| topologySpreadConstraints | list | `[]` | Topology spread constraints for pod assignment |
257258
| podAnnotations | object | `{}` | Pod annotations |
258259
| podLabels | object | `{}` | Pod labels |
259260
| env | list | `[]` | Additional environment variables |
@@ -278,6 +279,34 @@ env:
278279
value: "6443"
279280
```
280281

282+
Example topology spread constraints:
283+
```yaml
284+
# Spread pods across availability zones with best-effort scheduling
285+
topologySpreadConstraints:
286+
- maxSkew: 1
287+
topologyKey: topology.kubernetes.io/zone
288+
whenUnsatisfiable: ScheduleAnyway # Prefer spreading but allow scheduling even if it violates the constraint
289+
matchLabelKeys:
290+
- pod-template-hash
291+
- maxSkew: 1
292+
topologyKey: kubernetes.io/hostname
293+
whenUnsatisfiable: DoNotSchedule # Hard requirement - don't schedule if it violates the constraint
294+
matchLabelKeys:
295+
- pod-template-hash
296+
```
297+
298+
The `labelSelector` is automatically populated with the pod's selector labels if not specified. You can also provide a custom `labelSelector`:
299+
```yaml
300+
topologySpreadConstraints:
301+
- maxSkew: 1
302+
topologyKey: topology.kubernetes.io/zone
303+
whenUnsatisfiable: ScheduleAnyway
304+
labelSelector:
305+
matchLabels:
306+
app.kubernetes.io/name: headlamp
307+
custom-label: value
308+
```
309+
281310
### Pod Disruption Budget (PDB)
282311

283312
| Key | Type | Default | Description |

charts/headlamp/templates/deployment.yaml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -404,6 +404,17 @@ spec:
404404
tolerations:
405405
{{- toYaml . | nindent 8 }}
406406
{{- end }}
407+
{{- with .Values.topologySpreadConstraints }}
408+
topologySpreadConstraints:
409+
{{- range $constraint := . }}
410+
- {{ toYaml $constraint | nindent 10 }}
411+
{{- if not $constraint.labelSelector }}
412+
labelSelector:
413+
matchLabels:
414+
{{- include "headlamp.selectorLabels" $ | nindent 14 }}
415+
{{- end }}
416+
{{- end }}
417+
{{- end }}
407418
{{- with .Values.priorityClassName }}
408419
priorityClassName: {{ . | quote }}
409420
{{- end }}
Lines changed: 271 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,271 @@
1+
---
2+
# Source: headlamp/templates/serviceaccount.yaml
3+
apiVersion: v1
4+
kind: ServiceAccount
5+
metadata:
6+
name: headlamp
7+
namespace: default
8+
labels:
9+
helm.sh/chart: headlamp-0.39.0
10+
app.kubernetes.io/name: headlamp
11+
app.kubernetes.io/instance: headlamp
12+
app.kubernetes.io/version: "0.39.0"
13+
app.kubernetes.io/managed-by: Helm
14+
---
15+
# Source: headlamp/templates/secret.yaml
16+
apiVersion: v1
17+
kind: Secret
18+
metadata:
19+
name: oidc
20+
namespace: default
21+
type: Opaque
22+
data:
23+
---
24+
# Source: headlamp/templates/service.yaml
25+
apiVersion: v1
26+
kind: Service
27+
metadata:
28+
name: headlamp
29+
namespace: default
30+
labels:
31+
helm.sh/chart: headlamp-0.39.0
32+
app.kubernetes.io/name: headlamp
33+
app.kubernetes.io/instance: headlamp
34+
app.kubernetes.io/version: "0.39.0"
35+
app.kubernetes.io/managed-by: Helm
36+
spec:
37+
type: ClusterIP
38+
39+
ports:
40+
- port: 80
41+
targetPort: http
42+
protocol: TCP
43+
name: http
44+
selector:
45+
app.kubernetes.io/name: headlamp
46+
app.kubernetes.io/instance: headlamp
47+
---
48+
# Source: headlamp/templates/deployment.yaml
49+
# This block of code is used to extract the values from the env.
50+
# This is done to check if the values are non-empty and if they are, they are used in the deployment.yaml.
51+
52+
apiVersion: apps/v1
53+
kind: Deployment
54+
metadata:
55+
name: headlamp
56+
namespace: default
57+
labels:
58+
helm.sh/chart: headlamp-0.39.0
59+
app.kubernetes.io/name: headlamp
60+
app.kubernetes.io/instance: headlamp
61+
app.kubernetes.io/version: "0.39.0"
62+
app.kubernetes.io/managed-by: Helm
63+
spec:
64+
replicas: 1
65+
selector:
66+
matchLabels:
67+
app.kubernetes.io/name: headlamp
68+
app.kubernetes.io/instance: headlamp
69+
template:
70+
metadata:
71+
labels:
72+
app.kubernetes.io/name: headlamp
73+
app.kubernetes.io/instance: headlamp
74+
spec:
75+
serviceAccountName: headlamp
76+
automountServiceAccountToken: true
77+
hostUsers: true
78+
securityContext:
79+
{}
80+
containers:
81+
- name: headlamp
82+
securityContext:
83+
privileged: false
84+
runAsGroup: 101
85+
runAsNonRoot: true
86+
runAsUser: 100
87+
image: "ghcr.io/headlamp-k8s/headlamp:v0.39.0"
88+
imagePullPolicy: IfNotPresent
89+
90+
env:
91+
args:
92+
- "-in-cluster"
93+
- "-in-cluster-context-name=main"
94+
- "-plugins-dir=/headlamp/plugins"
95+
# Check if externalSecret is disabled
96+
ports:
97+
- name: http
98+
containerPort: 4466
99+
protocol: TCP
100+
livenessProbe:
101+
httpGet:
102+
path: "/"
103+
port: http
104+
readinessProbe:
105+
httpGet:
106+
path: "/"
107+
port: http
108+
resources:
109+
{}
110+
topologySpreadConstraints:
111+
-
112+
labelSelector:
113+
matchLabels:
114+
app.kubernetes.io/name: headlamp
115+
custom-label: custom-value
116+
matchLabelKeys:
117+
- pod-template-hash
118+
maxSkew: 2
119+
topologyKey: topology.kubernetes.io/zone
120+
whenUnsatisfiable: ScheduleAnyway
121+
---
122+
# Source: headlamp/templates/pre-upgrade-cleanup.yaml
123+
apiVersion: v1
124+
kind: ServiceAccount
125+
metadata:
126+
name: headlamp-pre-upgrade
127+
namespace: default
128+
labels:
129+
helm.sh/chart: headlamp-0.39.0
130+
app.kubernetes.io/name: headlamp
131+
app.kubernetes.io/instance: headlamp
132+
app.kubernetes.io/version: "0.39.0"
133+
app.kubernetes.io/managed-by: Helm
134+
annotations:
135+
"helm.sh/hook": pre-upgrade
136+
"helm.sh/hook-weight": "-5"
137+
"helm.sh/hook-delete-policy": before-hook-creation,hook-succeeded
138+
---
139+
# Source: headlamp/templates/pre-upgrade-cleanup.yaml
140+
apiVersion: rbac.authorization.k8s.io/v1
141+
kind: ClusterRole
142+
metadata:
143+
name: headlamp-pre-upgrade
144+
labels:
145+
helm.sh/chart: headlamp-0.39.0
146+
app.kubernetes.io/name: headlamp
147+
app.kubernetes.io/instance: headlamp
148+
app.kubernetes.io/version: "0.39.0"
149+
app.kubernetes.io/managed-by: Helm
150+
annotations:
151+
"helm.sh/hook": pre-upgrade
152+
"helm.sh/hook-weight": "-5"
153+
"helm.sh/hook-delete-policy": before-hook-creation,hook-succeeded
154+
rules:
155+
- apiGroups: ["rbac.authorization.k8s.io"]
156+
resources: ["clusterrolebindings"]
157+
verbs: ["get", "delete"]
158+
---
159+
# Source: headlamp/templates/pre-upgrade-cleanup.yaml
160+
apiVersion: rbac.authorization.k8s.io/v1
161+
kind: ClusterRoleBinding
162+
metadata:
163+
name: headlamp-pre-upgrade
164+
labels:
165+
helm.sh/chart: headlamp-0.39.0
166+
app.kubernetes.io/name: headlamp
167+
app.kubernetes.io/instance: headlamp
168+
app.kubernetes.io/version: "0.39.0"
169+
app.kubernetes.io/managed-by: Helm
170+
annotations:
171+
"helm.sh/hook": pre-upgrade
172+
"helm.sh/hook-weight": "-4"
173+
"helm.sh/hook-delete-policy": before-hook-creation,hook-succeeded
174+
roleRef:
175+
apiGroup: rbac.authorization.k8s.io
176+
kind: ClusterRole
177+
name: headlamp-pre-upgrade
178+
subjects:
179+
- kind: ServiceAccount
180+
name: headlamp-pre-upgrade
181+
namespace: default
182+
---
183+
# Source: headlamp/templates/pre-upgrade-cleanup.yaml
184+
apiVersion: batch/v1
185+
kind: Job
186+
metadata:
187+
name: headlamp-pre-upgrade
188+
namespace: default
189+
labels:
190+
helm.sh/chart: headlamp-0.39.0
191+
app.kubernetes.io/name: headlamp
192+
app.kubernetes.io/instance: headlamp
193+
app.kubernetes.io/version: "0.39.0"
194+
app.kubernetes.io/managed-by: Helm
195+
annotations:
196+
"helm.sh/hook": pre-upgrade
197+
"helm.sh/hook-weight": "-3"
198+
"helm.sh/hook-delete-policy": before-hook-creation,hook-succeeded
199+
spec:
200+
ttlSecondsAfterFinished: 300
201+
template:
202+
metadata:
203+
name: headlamp-pre-upgrade
204+
labels:
205+
helm.sh/chart: headlamp-0.39.0
206+
app.kubernetes.io/name: headlamp
207+
app.kubernetes.io/instance: headlamp
208+
app.kubernetes.io/version: "0.39.0"
209+
app.kubernetes.io/managed-by: Helm
210+
spec:
211+
serviceAccountName: headlamp-pre-upgrade
212+
restartPolicy: Never
213+
containers:
214+
- name: pre-upgrade-cleanup
215+
image: alpine/kubectl:1.35.0@sha256:e7e078c7bb25012141e5957d500834b2a5b266d6de20ecfa862b30d8a892fc7e
216+
command:
217+
- /bin/sh
218+
- -c
219+
- |
220+
set -e
221+
CRB_NAME="headlamp-admin"
222+
RELEASE_NAME="headlamp"
223+
224+
echo "Checking for old ClusterRoleBinding ${CRB_NAME}..."
225+
226+
if ! kubectl get clusterrolebinding "${CRB_NAME}" 2>/dev/null; then
227+
echo "ClusterRoleBinding ${CRB_NAME} not found, nothing to clean up"
228+
exit 0
229+
fi
230+
231+
echo "Found ClusterRoleBinding ${CRB_NAME}, verifying it was created by Helm..."
232+
233+
# Check if the ClusterRoleBinding has Helm labels indicating it was created by this chart
234+
MANAGED_BY=$(kubectl get clusterrolebinding "${CRB_NAME}" -o jsonpath='{.metadata.labels.app\.kubernetes\.io/managed-by}' 2>/dev/null || echo "")
235+
INSTANCE=$(kubectl get clusterrolebinding "${CRB_NAME}" -o jsonpath='{.metadata.labels.app\.kubernetes\.io/instance}' 2>/dev/null || echo "")
236+
APP_NAME=$(kubectl get clusterrolebinding "${CRB_NAME}" -o jsonpath='{.metadata.labels.app\.kubernetes\.io/name}' 2>/dev/null || echo "")
237+
238+
if [ "${MANAGED_BY}" = "Helm" ] && [ "${INSTANCE}" = "${RELEASE_NAME}" ] && [ "${APP_NAME}" = "headlamp" ]; then
239+
echo "Confirmed: ${CRB_NAME} was created by this Helm release (${RELEASE_NAME})"
240+
echo "Deleting old ClusterRoleBinding..."
241+
kubectl delete clusterrolebinding "${CRB_NAME}"
242+
echo "Successfully deleted old ClusterRoleBinding"
243+
else
244+
echo "WARNING: ${CRB_NAME} exists but was NOT created by this Helm release"
245+
echo " managed-by: ${MANAGED_BY} (expected: Helm)"
246+
echo " instance: ${INSTANCE} (expected: ${RELEASE_NAME})"
247+
echo " app: ${APP_NAME} (expected: headlamp)"
248+
echo "Skipping deletion to preserve user-created resource"
249+
fi
250+
resources:
251+
requests:
252+
cpu: 10m
253+
memory: 64Mi
254+
limits:
255+
cpu: 100m
256+
memory: 128Mi
257+
258+
securityContext:
259+
allowPrivilegeEscalation: false
260+
runAsNonRoot: true
261+
capabilities:
262+
drop:
263+
- ALL
264+
seccompProfile:
265+
type: RuntimeDefault
266+
volumeMounts:
267+
- name: tmp
268+
mountPath: /tmp
269+
volumes:
270+
- name: tmp
271+
emptyDir: {}

0 commit comments

Comments
 (0)