From 913086d4bf248051145188be7b93d35e5fc89d4a Mon Sep 17 00:00:00 2001 From: somaz Date: Thu, 9 Jul 2026 10:24:57 +0900 Subject: [PATCH] feat(matrix-stack): add affinity support to component workloads --- .../matrix-stack/source/common/affinity.json | 43 ++ .../source/common/sub_schema_values.yaml.j2 | 22 + .../source/deployment-markers.json | 3 + .../source/deployment-markers.yaml.j2 | 1 + charts/matrix-stack/source/element-admin.json | 3 + .../matrix-stack/source/element-admin.yaml.j2 | 1 + charts/matrix-stack/source/element-web.json | 3 + .../matrix-stack/source/element-web.yaml.j2 | 1 + charts/matrix-stack/source/haproxy.json | 3 + charts/matrix-stack/source/haproxy.yaml.j2 | 1 + charts/matrix-stack/source/hookshot.json | 3 + charts/matrix-stack/source/hookshot.yaml.j2 | 1 + charts/matrix-stack/source/init-secrets.json | 3 + .../matrix-stack/source/init-secrets.yaml.j2 | 1 + charts/matrix-stack/source/matrix-rtc.json | 6 + charts/matrix-stack/source/matrix-rtc.yaml.j2 | 2 + .../source/matrixAuthenticationService.json | 6 + .../matrixAuthenticationService.yaml.j2 | 2 + charts/matrix-stack/source/postgres.json | 3 + charts/matrix-stack/source/postgres.yaml.j2 | 1 + charts/matrix-stack/source/redis.json | 3 + charts/matrix-stack/source/redis.yaml.j2 | 1 + charts/matrix-stack/source/synapse.json | 3 + charts/matrix-stack/source/synapse.yaml.j2 | 1 + charts/matrix-stack/source/values.schema.json | 3 + .../templates/ess-library/_pods.tpl | 30 + charts/matrix-stack/values.schema.json | 602 ++++++++++++++++++ charts/matrix-stack/values.yaml | 156 +++++ newsfragments/1442.added.md | 1 + tests/manifests/__init__.py | 4 + tests/manifests/test_pod_affinity.py | 196 ++++++ 31 files changed, 1109 insertions(+) create mode 100644 charts/matrix-stack/source/common/affinity.json create mode 100644 newsfragments/1442.added.md create mode 100644 tests/manifests/test_pod_affinity.py diff --git a/charts/matrix-stack/source/common/affinity.json b/charts/matrix-stack/source/common/affinity.json new file mode 100644 index 000000000..f1d884961 --- /dev/null +++ b/charts/matrix-stack/source/common/affinity.json @@ -0,0 +1,43 @@ +{ + "type": "object", + "properties": { + "nodeAffinity": { + "type": "object", + "additionalProperties": false, + "properties": { + "requiredDuringSchedulingIgnoredDuringExecution": { + "type": "object", + "additionalProperties": true + }, + "preferredDuringSchedulingIgnoredDuringExecution": { + "type": "array" + } + } + }, + "podAffinity": { + "type": "object", + "additionalProperties": false, + "properties": { + "requiredDuringSchedulingIgnoredDuringExecution": { + "type": "array" + }, + "preferredDuringSchedulingIgnoredDuringExecution": { + "type": "array" + } + } + }, + "podAntiAffinity": { + "type": "object", + "additionalProperties": false, + "properties": { + "requiredDuringSchedulingIgnoredDuringExecution": { + "type": "array" + }, + "preferredDuringSchedulingIgnoredDuringExecution": { + "type": "array" + } + } + } + }, + "additionalProperties": false +} diff --git a/charts/matrix-stack/source/common/sub_schema_values.yaml.j2 b/charts/matrix-stack/source/common/sub_schema_values.yaml.j2 index 903996946..f4d0b1362 100644 --- a/charts/matrix-stack/source/common/sub_schema_values.yaml.j2 +++ b/charts/matrix-stack/source/common/sub_schema_values.yaml.j2 @@ -46,6 +46,7 @@ image: {{ tolerations(global=true) }} {{ topologySpreadConstraints(global=true) }} {{ priorityClassName(global=true) }} +{{ affinity(global=true) }} ## The cluster's domain name which will be appended to URLs of internal Services (..svc.). ## It is unusual to need to change this but if you do need to change it, @@ -230,6 +231,27 @@ networking: {{ key }}: "" {%- endmacro %} +{% macro affinity(global=false, key='affinity') %} +{%- if global %} +## Default affinity rules applied to the pods of all components. +## Configured independently per affinity type (nodeAffinity, podAffinity, podAntiAffinity). +## A component that sets one of these affinity types overrides only that type for that component; the other types are still inherited from here. +## Synapse worker pods inherit the affinity of the Synapse main process; per-worker affinity is not yet configurable. +{%- else %} +## If specified, the pod's scheduling constraints, configured independently per affinity type. +## Each affinity type set here overrides only the matching global affinity type for this component. +## Set an affinity type to {} to blank out the value inherited from the global affinity for that type. +{%- endif %} +## Supports nodeAffinity, podAffinity and podAntiAffinity. +## More info: https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/#affinity-and-anti-affinity +## Example: +## {{ key }}: +## nodeAffinity: {} +## podAffinity: {} +## podAntiAffinity: {} +{{ key }}: {} +{%- endmacro %} + {% macro persistentVolumeClaim(key, global=false) %} ## Configures the {{ 'defaults for all constructed PersistentVolumeClaims' if global else 'PersistentVolumeClaim to be used for storage' }} {{ key }}: diff --git a/charts/matrix-stack/source/deployment-markers.json b/charts/matrix-stack/source/deployment-markers.json index 35c43168d..99696a8d1 100644 --- a/charts/matrix-stack/source/deployment-markers.json +++ b/charts/matrix-stack/source/deployment-markers.json @@ -41,6 +41,9 @@ "priorityClassName": { "type": "string" }, + "affinity": { + "$ref": "file://common/affinity.json" + }, "podSecurityContext": { "$ref": "file://common/podSecurityContext.json" }, diff --git a/charts/matrix-stack/source/deployment-markers.yaml.j2 b/charts/matrix-stack/source/deployment-markers.yaml.j2 index 605bc6f71..342f3fd84 100644 --- a/charts/matrix-stack/source/deployment-markers.yaml.j2 +++ b/charts/matrix-stack/source/deployment-markers.yaml.j2 @@ -20,6 +20,7 @@ rbac: {{- sub_schema_values.extraInitContainers("Deployment Markers") }} {{- sub_schema_values.nodeSelector() -}} {{- sub_schema_values.priorityClassName() -}} +{{- sub_schema_values.affinity() -}} {{- sub_schema_values.podSecurityContext(user_id='10010', group_id='10010') -}} {{- sub_schema_values.resources(requests_memory='50Mi', requests_cpu='50m', limits_memory='200Mi') -}} {{- sub_schema_values.serviceAccount() -}} diff --git a/charts/matrix-stack/source/element-admin.json b/charts/matrix-stack/source/element-admin.json index 7e15fdd84..b00406a65 100644 --- a/charts/matrix-stack/source/element-admin.json +++ b/charts/matrix-stack/source/element-admin.json @@ -43,6 +43,9 @@ "priorityClassName": { "type": "string" }, + "affinity": { + "$ref": "file://common/affinity.json" + }, "podSecurityContext": { "$ref": "file://common/podSecurityContext.json" }, diff --git a/charts/matrix-stack/source/element-admin.yaml.j2 b/charts/matrix-stack/source/element-admin.yaml.j2 index 952cc5103..7e87a71ee 100644 --- a/charts/matrix-stack/source/element-admin.yaml.j2 +++ b/charts/matrix-stack/source/element-admin.yaml.j2 @@ -23,6 +23,7 @@ replicas: 1 {{- sub_schema_values.containersSecurityContext() -}} {{- sub_schema_values.nodeSelector() -}} {{- sub_schema_values.priorityClassName() -}} +{{- sub_schema_values.affinity() -}} {{- sub_schema_values.podSecurityContext(user_id='10104', group_id='10104') -}} {{- sub_schema_values.resources(requests_memory='50Mi', requests_cpu='50m', limits_memory='200Mi') -}} {{- sub_schema_values.serviceAccount() -}} diff --git a/charts/matrix-stack/source/element-web.json b/charts/matrix-stack/source/element-web.json index c3162f310..fd65a8b45 100644 --- a/charts/matrix-stack/source/element-web.json +++ b/charts/matrix-stack/source/element-web.json @@ -49,6 +49,9 @@ "priorityClassName": { "type": "string" }, + "affinity": { + "$ref": "file://common/affinity.json" + }, "podSecurityContext": { "$ref": "file://common/podSecurityContext.json" }, diff --git a/charts/matrix-stack/source/element-web.yaml.j2 b/charts/matrix-stack/source/element-web.yaml.j2 index 090016543..18f0a0e54 100644 --- a/charts/matrix-stack/source/element-web.yaml.j2 +++ b/charts/matrix-stack/source/element-web.yaml.j2 @@ -31,6 +31,7 @@ replicas: 1 {{- sub_schema_values.containersSecurityContext() -}} {{- sub_schema_values.nodeSelector() -}} {{- sub_schema_values.priorityClassName() -}} +{{- sub_schema_values.affinity() -}} {{- sub_schema_values.podSecurityContext(user_id='10004', group_id='10004') -}} {{- sub_schema_values.resources(requests_memory='50Mi', requests_cpu='50m', limits_memory='200Mi') -}} {{- sub_schema_values.serviceAccount() -}} diff --git a/charts/matrix-stack/source/haproxy.json b/charts/matrix-stack/source/haproxy.json index 229acc993..f4533e4c7 100644 --- a/charts/matrix-stack/source/haproxy.json +++ b/charts/matrix-stack/source/haproxy.json @@ -37,6 +37,9 @@ "priorityClassName": { "type": "string" }, + "affinity": { + "$ref": "file://common/affinity.json" + }, "podSecurityContext": { "$ref": "file://common/podSecurityContext.json" }, diff --git a/charts/matrix-stack/source/haproxy.yaml.j2 b/charts/matrix-stack/source/haproxy.yaml.j2 index c7e319e49..fdd39286d 100644 --- a/charts/matrix-stack/source/haproxy.yaml.j2 +++ b/charts/matrix-stack/source/haproxy.yaml.j2 @@ -18,6 +18,7 @@ replicas: 1 {{- sub_schema_values.extraInitContainers("HAProxy") }} {{- sub_schema_values.nodeSelector() }} {{- sub_schema_values.priorityClassName() }} +{{- sub_schema_values.affinity() }} {{- sub_schema_values.podSecurityContext(user_id='10001', group_id='10001') }} {{- sub_schema_values.resources(requests_memory='100Mi', requests_cpu='100m', limits_memory='200Mi') }} {{- sub_schema_values.serviceAccount() }} diff --git a/charts/matrix-stack/source/hookshot.json b/charts/matrix-stack/source/hookshot.json index dc7d628fe..5aa99daf9 100644 --- a/charts/matrix-stack/source/hookshot.json +++ b/charts/matrix-stack/source/hookshot.json @@ -92,6 +92,9 @@ "priorityClassName": { "type": "string" }, + "affinity": { + "$ref": "file://common/affinity.json" + }, "podSecurityContext": { "$ref": "file://common/podSecurityContext.json" }, diff --git a/charts/matrix-stack/source/hookshot.yaml.j2 b/charts/matrix-stack/source/hookshot.yaml.j2 index 7b9a466b5..8116a9926 100644 --- a/charts/matrix-stack/source/hookshot.yaml.j2 +++ b/charts/matrix-stack/source/hookshot.yaml.j2 @@ -42,6 +42,7 @@ replicas: 1 {{ sub_schema_values.serviceAccount() }} {{ sub_schema_values.nodeSelector() }} {{- sub_schema_values.priorityClassName() }} +{{- sub_schema_values.affinity() }} {{ sub_schema_values.tolerations() }} {{ sub_schema_values.topologySpreadConstraints() }} {{ sub_schema_values.podSecurityContext(user_id=10007, group_id=10007) }} diff --git a/charts/matrix-stack/source/init-secrets.json b/charts/matrix-stack/source/init-secrets.json index 7f71b7c95..6eb80aecc 100644 --- a/charts/matrix-stack/source/init-secrets.json +++ b/charts/matrix-stack/source/init-secrets.json @@ -41,6 +41,9 @@ "priorityClassName": { "type": "string" }, + "affinity": { + "$ref": "file://common/affinity.json" + }, "podSecurityContext": { "$ref": "file://common/podSecurityContext.json" }, diff --git a/charts/matrix-stack/source/init-secrets.yaml.j2 b/charts/matrix-stack/source/init-secrets.yaml.j2 index ee1ac55a4..c43045824 100644 --- a/charts/matrix-stack/source/init-secrets.yaml.j2 +++ b/charts/matrix-stack/source/init-secrets.yaml.j2 @@ -20,6 +20,7 @@ rbac: {{- sub_schema_values.extraInitContainers("Init Secrets") }} {{- sub_schema_values.nodeSelector() -}} {{- sub_schema_values.priorityClassName() -}} +{{- sub_schema_values.affinity() -}} {{- sub_schema_values.podSecurityContext(user_id='10010', group_id='10010') -}} {{- sub_schema_values.resources(requests_memory='50Mi', requests_cpu='50m', limits_memory='200Mi') -}} {{- sub_schema_values.serviceAccount() -}} diff --git a/charts/matrix-stack/source/matrix-rtc.json b/charts/matrix-stack/source/matrix-rtc.json index 2dcf75618..dc0d02912 100644 --- a/charts/matrix-stack/source/matrix-rtc.json +++ b/charts/matrix-stack/source/matrix-rtc.json @@ -90,6 +90,9 @@ "priorityClassName": { "type": "string" }, + "affinity": { + "$ref": "file://common/affinity.json" + }, "podSecurityContext": { "$ref": "file://common/podSecurityContext.json" }, @@ -213,6 +216,9 @@ "priorityClassName": { "type": "string" }, + "affinity": { + "$ref": "file://common/affinity.json" + }, "podSecurityContext": { "$ref": "file://common/podSecurityContext.json" }, diff --git a/charts/matrix-stack/source/matrix-rtc.yaml.j2 b/charts/matrix-stack/source/matrix-rtc.yaml.j2 index 983a59dff..fae1a0783 100644 --- a/charts/matrix-stack/source/matrix-rtc.yaml.j2 +++ b/charts/matrix-stack/source/matrix-rtc.yaml.j2 @@ -35,6 +35,7 @@ replicas: 1 {{- sub_schema_values.extraInitContainers("Matrix RTC") }} {{- sub_schema_values.nodeSelector() }} {{- sub_schema_values.priorityClassName() }} +{{- sub_schema_values.affinity() }} {{- sub_schema_values.podSecurityContext(user_id='10033', group_id='10033') }} {{- sub_schema_values.resources(requests_memory='50Mi', requests_cpu='50m', limits_memory='200Mi') }} {{- sub_schema_values.serviceMonitors() }} @@ -92,6 +93,7 @@ sfu: {{- sub_schema_values.containersSecurityContext() | indent(2) }} {{- sub_schema_values.nodeSelector() | indent(2) }} {{- sub_schema_values.priorityClassName() | indent(2) }} +{{- sub_schema_values.affinity() | indent(2) }} {{- sub_schema_values.podSecurityContext(user_id='10030', group_id='10030') | indent(2) }} {{- sub_schema_values.resources(requests_memory='150Mi', requests_cpu='100m', limits_memory='4Gi') | indent(2) }} {{- sub_schema_values.serviceAccount() | indent(2) }} diff --git a/charts/matrix-stack/source/matrixAuthenticationService.json b/charts/matrix-stack/source/matrixAuthenticationService.json index 84a8b648f..3eba28ec6 100644 --- a/charts/matrix-stack/source/matrixAuthenticationService.json +++ b/charts/matrix-stack/source/matrixAuthenticationService.json @@ -83,6 +83,9 @@ "priorityClassName": { "type": "string" }, + "affinity": { + "$ref": "file://common/affinity.json" + }, "podSecurityContext": { "$ref": "file://common/podSecurityContext.json" }, @@ -136,6 +139,9 @@ "priorityClassName": { "type": "string" }, + "affinity": { + "$ref": "file://common/affinity.json" + }, "podSecurityContext": { "$ref": "file://common/podSecurityContext.json" }, diff --git a/charts/matrix-stack/source/matrixAuthenticationService.yaml.j2 b/charts/matrix-stack/source/matrixAuthenticationService.yaml.j2 index 54020fcc8..e48fed583 100644 --- a/charts/matrix-stack/source/matrixAuthenticationService.yaml.j2 +++ b/charts/matrix-stack/source/matrixAuthenticationService.yaml.j2 @@ -32,6 +32,7 @@ privateKeys: {} {{ sub_schema_values.serviceAccount() }} {{ sub_schema_values.nodeSelector() }} {{- sub_schema_values.priorityClassName() }} +{{- sub_schema_values.affinity() }} {{ sub_schema_values.tolerations() }} {{ sub_schema_values.hostAliases() }} {{ sub_schema_values.topologySpreadConstraints() }} @@ -70,6 +71,7 @@ syn2mas: {{- sub_schema_values.containersSecurityContext() | indent(2) -}} {{- sub_schema_values.nodeSelector() | indent(2) -}} {{- sub_schema_values.priorityClassName() | indent(2) -}} + {{- sub_schema_values.affinity() | indent(2) -}} {{- sub_schema_values.podSecurityContext(user_id='10005', group_id='10005') | indent(2) -}} {{- sub_schema_values.resources(requests_memory='50Mi', requests_cpu='50m', limits_memory='350Mi') | indent(2) -}} {{- sub_schema_values.serviceAccount() | indent(2) -}} diff --git a/charts/matrix-stack/source/postgres.json b/charts/matrix-stack/source/postgres.json index 18216165d..31c24d977 100644 --- a/charts/matrix-stack/source/postgres.json +++ b/charts/matrix-stack/source/postgres.json @@ -90,6 +90,9 @@ "priorityClassName": { "type": "string" }, + "affinity": { + "$ref": "file://common/affinity.json" + }, "podSecurityContext": { "$ref": "file://common/podSecurityContext.json" }, diff --git a/charts/matrix-stack/source/postgres.yaml.j2 b/charts/matrix-stack/source/postgres.yaml.j2 index f5cb8b413..c5526e294 100644 --- a/charts/matrix-stack/source/postgres.yaml.j2 +++ b/charts/matrix-stack/source/postgres.yaml.j2 @@ -36,6 +36,7 @@ essPasswords: {} {{- sub_schema_values.containersSecurityContext() }} {{- sub_schema_values.nodeSelector() }} {{- sub_schema_values.priorityClassName() }} +{{- sub_schema_values.affinity() }} {{- sub_schema_values.podSecurityContext(user_id='10091', group_id='10091') }} {{- sub_schema_values.resources(requests_memory='100Mi', requests_cpu='100m', limits_memory='4Gi') }} {{- sub_schema_values.serviceAccount() }} diff --git a/charts/matrix-stack/source/redis.json b/charts/matrix-stack/source/redis.json index e94bb8779..daa52c641 100644 --- a/charts/matrix-stack/source/redis.json +++ b/charts/matrix-stack/source/redis.json @@ -54,6 +54,9 @@ "priorityClassName": { "type": "string" }, + "affinity": { + "$ref": "file://common/affinity.json" + }, "podSecurityContext": { "$ref": "file://common/podSecurityContext.json" }, diff --git a/charts/matrix-stack/source/redis.yaml.j2 b/charts/matrix-stack/source/redis.yaml.j2 index c65523e98..22b79bedd 100644 --- a/charts/matrix-stack/source/redis.yaml.j2 +++ b/charts/matrix-stack/source/redis.yaml.j2 @@ -22,6 +22,7 @@ replicas: 1 {{- sub_schema_values.extraInitContainers("Redis") }} {{- sub_schema_values.nodeSelector() }} {{- sub_schema_values.priorityClassName() }} +{{- sub_schema_values.affinity() }} {{- sub_schema_values.podSecurityContext(user_id='10002', group_id='10002') }} {{- sub_schema_values.resources(requests_memory='50Mi', requests_cpu='50m', limits_memory='50Mi') }} {{- sub_schema_values.serviceAccount() }} diff --git a/charts/matrix-stack/source/synapse.json b/charts/matrix-stack/source/synapse.json index 899870203..4dd716987 100644 --- a/charts/matrix-stack/source/synapse.json +++ b/charts/matrix-stack/source/synapse.json @@ -168,6 +168,9 @@ "priorityClassName": { "type": "string" }, + "affinity": { + "$ref": "file://common/affinity.json" + }, "extraVolumes": { "$ref": "file://common/extraVolumesWithContext.json" }, diff --git a/charts/matrix-stack/source/synapse.yaml.j2 b/charts/matrix-stack/source/synapse.yaml.j2 index 7e6e2c08b..5fa55cbc8 100644 --- a/charts/matrix-stack/source/synapse.yaml.j2 +++ b/charts/matrix-stack/source/synapse.yaml.j2 @@ -102,6 +102,7 @@ replicas: 1 {{- sub_schema_values.hostAliases() }} {{- sub_schema_values.nodeSelector() }} {{- sub_schema_values.priorityClassName() }} +{{- sub_schema_values.affinity() }} {{- sub_schema_values.podSecurityContext(user_id='10091', group_id='10091') }} {{- sub_schema_values.resources(requests_memory='100Mi', requests_cpu='100m', limits_memory='4Gi') }} {{- sub_schema_values.serviceAccount() }} diff --git a/charts/matrix-stack/source/values.schema.json b/charts/matrix-stack/source/values.schema.json index fad2e43b7..2fab8af10 100644 --- a/charts/matrix-stack/source/values.schema.json +++ b/charts/matrix-stack/source/values.schema.json @@ -91,6 +91,9 @@ "priorityClassName": { "type": "string" }, + "affinity": { + "$ref": "file://common/affinity.json" + }, "clusterDomain": { "type": "string" }, diff --git a/charts/matrix-stack/templates/ess-library/_pods.tpl b/charts/matrix-stack/templates/ess-library/_pods.tpl index 23c98b90b..7e6459697 100644 --- a/charts/matrix-stack/templates/ess-library/_pods.tpl +++ b/charts/matrix-stack/templates/ess-library/_pods.tpl @@ -31,6 +31,7 @@ nodeSelector: {{- with (coalesce .priorityClassName $root.Values.priorityClassName) }} priorityClassName: {{ . }} {{- end }} +{{- include "element-io.ess-library.pods.affinity" (dict "root" $root "context" (dict "affinity" .affinity)) }} restartPolicy: {{ (eq $kind "Job") | ternary "Never" "Always" }} {{- include "element-io.ess-library.pods.tolerations" (dict "root" $root "context" .tolerations) }} {{- include "element-io.ess-library.pods.topologySpreadConstraints" (dict "root" $root "context" (dict "instanceSuffix" $instanceSuffix "deployment" (eq $kind "Deployment") "topologySpreadConstraints" .topologySpreadConstraints)) }} @@ -103,6 +104,35 @@ topologySpreadConstraints: {{- end }} {{- end }} +{{- define "element-io.ess-library.pods.affinity" -}} +{{- $root := .root -}} +{{- with required "element-io.ess-library.pods.affinity missing context" .context -}} +{{- $componentAffinity := .affinity | default dict -}} +{{- $globalAffinity := $root.Values.affinity | default dict -}} +{{- $affinity := dict -}} +{{- /* +Each affinity type is resolved independently so that setting one type at a component level does not drop +the other types inherited from the global affinity. A component that sets an affinity type overrides only +that type; setting it to an empty value ({} or null) blanks out the inherited global value for that type. +*/ -}} +{{- range $affinityType := (list "nodeAffinity" "podAffinity" "podAntiAffinity") -}} +{{- if hasKey $componentAffinity $affinityType -}} +{{- with (get $componentAffinity $affinityType) -}} +{{- $_ := set $affinity $affinityType . -}} +{{- end -}} +{{- else -}} +{{- with (get $globalAffinity $affinityType) -}} +{{- $_ := set $affinity $affinityType . -}} +{{- end -}} +{{- end -}} +{{- end -}} +{{- with $affinity }} +affinity: + {{- toYaml . | nindent 2 }} +{{- end }} +{{- end -}} +{{- end -}} + {{- /* The only differences this has over (. | toYaml) are that * it allows us to omit nullable values for individual properties diff --git a/charts/matrix-stack/values.schema.json b/charts/matrix-stack/values.schema.json index 4fbb3855b..2af36e304 100644 --- a/charts/matrix-stack/values.schema.json +++ b/charts/matrix-stack/values.schema.json @@ -375,6 +375,49 @@ "priorityClassName": { "type": "string" }, + "affinity": { + "type": "object", + "properties": { + "nodeAffinity": { + "type": "object", + "additionalProperties": false, + "properties": { + "requiredDuringSchedulingIgnoredDuringExecution": { + "type": "object", + "additionalProperties": true + }, + "preferredDuringSchedulingIgnoredDuringExecution": { + "type": "array" + } + } + }, + "podAffinity": { + "type": "object", + "additionalProperties": false, + "properties": { + "requiredDuringSchedulingIgnoredDuringExecution": { + "type": "array" + }, + "preferredDuringSchedulingIgnoredDuringExecution": { + "type": "array" + } + } + }, + "podAntiAffinity": { + "type": "object", + "additionalProperties": false, + "properties": { + "requiredDuringSchedulingIgnoredDuringExecution": { + "type": "array" + }, + "preferredDuringSchedulingIgnoredDuringExecution": { + "type": "array" + } + } + } + }, + "additionalProperties": false + }, "clusterDomain": { "type": "string" }, @@ -572,6 +615,49 @@ "priorityClassName": { "type": "string" }, + "affinity": { + "type": "object", + "properties": { + "nodeAffinity": { + "type": "object", + "additionalProperties": false, + "properties": { + "requiredDuringSchedulingIgnoredDuringExecution": { + "type": "object", + "additionalProperties": true + }, + "preferredDuringSchedulingIgnoredDuringExecution": { + "type": "array" + } + } + }, + "podAffinity": { + "type": "object", + "additionalProperties": false, + "properties": { + "requiredDuringSchedulingIgnoredDuringExecution": { + "type": "array" + }, + "preferredDuringSchedulingIgnoredDuringExecution": { + "type": "array" + } + } + }, + "podAntiAffinity": { + "type": "object", + "additionalProperties": false, + "properties": { + "requiredDuringSchedulingIgnoredDuringExecution": { + "type": "array" + }, + "preferredDuringSchedulingIgnoredDuringExecution": { + "type": "array" + } + } + } + }, + "additionalProperties": false + }, "podSecurityContext": { "properties": { "fsGroup": { @@ -1005,6 +1091,49 @@ "priorityClassName": { "type": "string" }, + "affinity": { + "type": "object", + "properties": { + "nodeAffinity": { + "type": "object", + "additionalProperties": false, + "properties": { + "requiredDuringSchedulingIgnoredDuringExecution": { + "type": "object", + "additionalProperties": true + }, + "preferredDuringSchedulingIgnoredDuringExecution": { + "type": "array" + } + } + }, + "podAffinity": { + "type": "object", + "additionalProperties": false, + "properties": { + "requiredDuringSchedulingIgnoredDuringExecution": { + "type": "array" + }, + "preferredDuringSchedulingIgnoredDuringExecution": { + "type": "array" + } + } + }, + "podAntiAffinity": { + "type": "object", + "additionalProperties": false, + "properties": { + "requiredDuringSchedulingIgnoredDuringExecution": { + "type": "array" + }, + "preferredDuringSchedulingIgnoredDuringExecution": { + "type": "array" + } + } + } + }, + "additionalProperties": false + }, "podSecurityContext": { "properties": { "fsGroup": { @@ -1725,6 +1854,49 @@ "priorityClassName": { "type": "string" }, + "affinity": { + "type": "object", + "properties": { + "nodeAffinity": { + "type": "object", + "additionalProperties": false, + "properties": { + "requiredDuringSchedulingIgnoredDuringExecution": { + "type": "object", + "additionalProperties": true + }, + "preferredDuringSchedulingIgnoredDuringExecution": { + "type": "array" + } + } + }, + "podAffinity": { + "type": "object", + "additionalProperties": false, + "properties": { + "requiredDuringSchedulingIgnoredDuringExecution": { + "type": "array" + }, + "preferredDuringSchedulingIgnoredDuringExecution": { + "type": "array" + } + } + }, + "podAntiAffinity": { + "type": "object", + "additionalProperties": false, + "properties": { + "requiredDuringSchedulingIgnoredDuringExecution": { + "type": "array" + }, + "preferredDuringSchedulingIgnoredDuringExecution": { + "type": "array" + } + } + } + }, + "additionalProperties": false + }, "podSecurityContext": { "properties": { "fsGroup": { @@ -2724,6 +2896,49 @@ "priorityClassName": { "type": "string" }, + "affinity": { + "type": "object", + "properties": { + "nodeAffinity": { + "type": "object", + "additionalProperties": false, + "properties": { + "requiredDuringSchedulingIgnoredDuringExecution": { + "type": "object", + "additionalProperties": true + }, + "preferredDuringSchedulingIgnoredDuringExecution": { + "type": "array" + } + } + }, + "podAffinity": { + "type": "object", + "additionalProperties": false, + "properties": { + "requiredDuringSchedulingIgnoredDuringExecution": { + "type": "array" + }, + "preferredDuringSchedulingIgnoredDuringExecution": { + "type": "array" + } + } + }, + "podAntiAffinity": { + "type": "object", + "additionalProperties": false, + "properties": { + "requiredDuringSchedulingIgnoredDuringExecution": { + "type": "array" + }, + "preferredDuringSchedulingIgnoredDuringExecution": { + "type": "array" + } + } + } + }, + "additionalProperties": false + }, "podSecurityContext": { "properties": { "fsGroup": { @@ -3438,6 +3653,49 @@ "priorityClassName": { "type": "string" }, + "affinity": { + "type": "object", + "properties": { + "nodeAffinity": { + "type": "object", + "additionalProperties": false, + "properties": { + "requiredDuringSchedulingIgnoredDuringExecution": { + "type": "object", + "additionalProperties": true + }, + "preferredDuringSchedulingIgnoredDuringExecution": { + "type": "array" + } + } + }, + "podAffinity": { + "type": "object", + "additionalProperties": false, + "properties": { + "requiredDuringSchedulingIgnoredDuringExecution": { + "type": "array" + }, + "preferredDuringSchedulingIgnoredDuringExecution": { + "type": "array" + } + } + }, + "podAntiAffinity": { + "type": "object", + "additionalProperties": false, + "properties": { + "requiredDuringSchedulingIgnoredDuringExecution": { + "type": "array" + }, + "preferredDuringSchedulingIgnoredDuringExecution": { + "type": "array" + } + } + } + }, + "additionalProperties": false + }, "podSecurityContext": { "properties": { "fsGroup": { @@ -4146,6 +4404,49 @@ "priorityClassName": { "type": "string" }, + "affinity": { + "type": "object", + "properties": { + "nodeAffinity": { + "type": "object", + "additionalProperties": false, + "properties": { + "requiredDuringSchedulingIgnoredDuringExecution": { + "type": "object", + "additionalProperties": true + }, + "preferredDuringSchedulingIgnoredDuringExecution": { + "type": "array" + } + } + }, + "podAffinity": { + "type": "object", + "additionalProperties": false, + "properties": { + "requiredDuringSchedulingIgnoredDuringExecution": { + "type": "array" + }, + "preferredDuringSchedulingIgnoredDuringExecution": { + "type": "array" + } + } + }, + "podAntiAffinity": { + "type": "object", + "additionalProperties": false, + "properties": { + "requiredDuringSchedulingIgnoredDuringExecution": { + "type": "array" + }, + "preferredDuringSchedulingIgnoredDuringExecution": { + "type": "array" + } + } + } + }, + "additionalProperties": false + }, "podSecurityContext": { "properties": { "fsGroup": { @@ -4769,6 +5070,49 @@ "priorityClassName": { "type": "string" }, + "affinity": { + "type": "object", + "properties": { + "nodeAffinity": { + "type": "object", + "additionalProperties": false, + "properties": { + "requiredDuringSchedulingIgnoredDuringExecution": { + "type": "object", + "additionalProperties": true + }, + "preferredDuringSchedulingIgnoredDuringExecution": { + "type": "array" + } + } + }, + "podAffinity": { + "type": "object", + "additionalProperties": false, + "properties": { + "requiredDuringSchedulingIgnoredDuringExecution": { + "type": "array" + }, + "preferredDuringSchedulingIgnoredDuringExecution": { + "type": "array" + } + } + }, + "podAntiAffinity": { + "type": "object", + "additionalProperties": false, + "properties": { + "requiredDuringSchedulingIgnoredDuringExecution": { + "type": "array" + }, + "preferredDuringSchedulingIgnoredDuringExecution": { + "type": "array" + } + } + } + }, + "additionalProperties": false + }, "podSecurityContext": { "properties": { "fsGroup": { @@ -5794,6 +6138,49 @@ "priorityClassName": { "type": "string" }, + "affinity": { + "type": "object", + "properties": { + "nodeAffinity": { + "type": "object", + "additionalProperties": false, + "properties": { + "requiredDuringSchedulingIgnoredDuringExecution": { + "type": "object", + "additionalProperties": true + }, + "preferredDuringSchedulingIgnoredDuringExecution": { + "type": "array" + } + } + }, + "podAffinity": { + "type": "object", + "additionalProperties": false, + "properties": { + "requiredDuringSchedulingIgnoredDuringExecution": { + "type": "array" + }, + "preferredDuringSchedulingIgnoredDuringExecution": { + "type": "array" + } + } + }, + "podAntiAffinity": { + "type": "object", + "additionalProperties": false, + "properties": { + "requiredDuringSchedulingIgnoredDuringExecution": { + "type": "array" + }, + "preferredDuringSchedulingIgnoredDuringExecution": { + "type": "array" + } + } + } + }, + "additionalProperties": false + }, "podSecurityContext": { "properties": { "fsGroup": { @@ -6801,6 +7188,49 @@ "priorityClassName": { "type": "string" }, + "affinity": { + "type": "object", + "properties": { + "nodeAffinity": { + "type": "object", + "additionalProperties": false, + "properties": { + "requiredDuringSchedulingIgnoredDuringExecution": { + "type": "object", + "additionalProperties": true + }, + "preferredDuringSchedulingIgnoredDuringExecution": { + "type": "array" + } + } + }, + "podAffinity": { + "type": "object", + "additionalProperties": false, + "properties": { + "requiredDuringSchedulingIgnoredDuringExecution": { + "type": "array" + }, + "preferredDuringSchedulingIgnoredDuringExecution": { + "type": "array" + } + } + }, + "podAntiAffinity": { + "type": "object", + "additionalProperties": false, + "properties": { + "requiredDuringSchedulingIgnoredDuringExecution": { + "type": "array" + }, + "preferredDuringSchedulingIgnoredDuringExecution": { + "type": "array" + } + } + } + }, + "additionalProperties": false + }, "podSecurityContext": { "properties": { "fsGroup": { @@ -7386,6 +7816,49 @@ "priorityClassName": { "type": "string" }, + "affinity": { + "type": "object", + "properties": { + "nodeAffinity": { + "type": "object", + "additionalProperties": false, + "properties": { + "requiredDuringSchedulingIgnoredDuringExecution": { + "type": "object", + "additionalProperties": true + }, + "preferredDuringSchedulingIgnoredDuringExecution": { + "type": "array" + } + } + }, + "podAffinity": { + "type": "object", + "additionalProperties": false, + "properties": { + "requiredDuringSchedulingIgnoredDuringExecution": { + "type": "array" + }, + "preferredDuringSchedulingIgnoredDuringExecution": { + "type": "array" + } + } + }, + "podAntiAffinity": { + "type": "object", + "additionalProperties": false, + "properties": { + "requiredDuringSchedulingIgnoredDuringExecution": { + "type": "array" + }, + "preferredDuringSchedulingIgnoredDuringExecution": { + "type": "array" + } + } + } + }, + "additionalProperties": false + }, "podSecurityContext": { "properties": { "fsGroup": { @@ -8577,6 +9050,49 @@ "priorityClassName": { "type": "string" }, + "affinity": { + "type": "object", + "properties": { + "nodeAffinity": { + "type": "object", + "additionalProperties": false, + "properties": { + "requiredDuringSchedulingIgnoredDuringExecution": { + "type": "object", + "additionalProperties": true + }, + "preferredDuringSchedulingIgnoredDuringExecution": { + "type": "array" + } + } + }, + "podAffinity": { + "type": "object", + "additionalProperties": false, + "properties": { + "requiredDuringSchedulingIgnoredDuringExecution": { + "type": "array" + }, + "preferredDuringSchedulingIgnoredDuringExecution": { + "type": "array" + } + } + }, + "podAntiAffinity": { + "type": "object", + "additionalProperties": false, + "properties": { + "requiredDuringSchedulingIgnoredDuringExecution": { + "type": "array" + }, + "preferredDuringSchedulingIgnoredDuringExecution": { + "type": "array" + } + } + } + }, + "additionalProperties": false + }, "podSecurityContext": { "properties": { "fsGroup": { @@ -9226,6 +9742,49 @@ "priorityClassName": { "type": "string" }, + "affinity": { + "type": "object", + "properties": { + "nodeAffinity": { + "type": "object", + "additionalProperties": false, + "properties": { + "requiredDuringSchedulingIgnoredDuringExecution": { + "type": "object", + "additionalProperties": true + }, + "preferredDuringSchedulingIgnoredDuringExecution": { + "type": "array" + } + } + }, + "podAffinity": { + "type": "object", + "additionalProperties": false, + "properties": { + "requiredDuringSchedulingIgnoredDuringExecution": { + "type": "array" + }, + "preferredDuringSchedulingIgnoredDuringExecution": { + "type": "array" + } + } + }, + "podAntiAffinity": { + "type": "object", + "additionalProperties": false, + "properties": { + "requiredDuringSchedulingIgnoredDuringExecution": { + "type": "array" + }, + "preferredDuringSchedulingIgnoredDuringExecution": { + "type": "array" + } + } + } + }, + "additionalProperties": false + }, "podSecurityContext": { "properties": { "fsGroup": { @@ -10857,6 +11416,49 @@ "priorityClassName": { "type": "string" }, + "affinity": { + "type": "object", + "properties": { + "nodeAffinity": { + "type": "object", + "additionalProperties": false, + "properties": { + "requiredDuringSchedulingIgnoredDuringExecution": { + "type": "object", + "additionalProperties": true + }, + "preferredDuringSchedulingIgnoredDuringExecution": { + "type": "array" + } + } + }, + "podAffinity": { + "type": "object", + "additionalProperties": false, + "properties": { + "requiredDuringSchedulingIgnoredDuringExecution": { + "type": "array" + }, + "preferredDuringSchedulingIgnoredDuringExecution": { + "type": "array" + } + } + }, + "podAntiAffinity": { + "type": "object", + "additionalProperties": false, + "properties": { + "requiredDuringSchedulingIgnoredDuringExecution": { + "type": "array" + }, + "preferredDuringSchedulingIgnoredDuringExecution": { + "type": "array" + } + } + } + }, + "additionalProperties": false + }, "extraVolumes": { "type": "array", "items": { diff --git a/charts/matrix-stack/values.yaml b/charts/matrix-stack/values.yaml index 552fa226a..4ea55154c 100644 --- a/charts/matrix-stack/values.yaml +++ b/charts/matrix-stack/values.yaml @@ -144,6 +144,19 @@ topologySpreadConstraints: ## More info: https://kubernetes.io/docs/concepts/scheduling-eviction/pod-priority-preemption/ priorityClassName: "" +## Default affinity rules applied to the pods of all components. +## Configured independently per affinity type (nodeAffinity, podAffinity, podAntiAffinity). +## A component that sets one of these affinity types overrides only that type for that component; the other types are still inherited from here. +## Synapse worker pods inherit the affinity of the Synapse main process; per-worker affinity is not yet configurable. +## Supports nodeAffinity, podAffinity and podAntiAffinity. +## More info: https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/#affinity-and-anti-affinity +## Example: +## affinity: +## nodeAffinity: {} +## podAffinity: {} +## podAntiAffinity: {} +affinity: {} + ## The cluster's domain name which will be appended to URLs of internal Services (..svc.). ## It is unusual to need to change this but if you do need to change it, ## strongly consider having a trailing dot to prevent multiple search domains being queried. @@ -210,6 +223,17 @@ initSecrets: ## If not set (empty), the pod's priority defaults to the cluster's globalDefault PriorityClass or zero. ## More info: https://kubernetes.io/docs/concepts/scheduling-eviction/pod-priority-preemption/ priorityClassName: "" + ## If specified, the pod's scheduling constraints, configured independently per affinity type. + ## Each affinity type set here overrides only the matching global affinity type for this component. + ## Set an affinity type to {} to blank out the value inherited from the global affinity for that type. + ## Supports nodeAffinity, podAffinity and podAntiAffinity. + ## More info: https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/#affinity-and-anti-affinity + ## Example: + ## affinity: + ## nodeAffinity: {} + ## podAffinity: {} + ## podAntiAffinity: {} + affinity: {} ## A subset of PodSecurityContext. PodSecurityContext holds pod-level security attributes and common container settings podSecurityContext: ## A special supplemental group that applies to all containers in a pod. Some volume types allow the Kubelet to @@ -365,6 +389,17 @@ deploymentMarkers: ## If not set (empty), the pod's priority defaults to the cluster's globalDefault PriorityClass or zero. ## More info: https://kubernetes.io/docs/concepts/scheduling-eviction/pod-priority-preemption/ priorityClassName: "" + ## If specified, the pod's scheduling constraints, configured independently per affinity type. + ## Each affinity type set here overrides only the matching global affinity type for this component. + ## Set an affinity type to {} to blank out the value inherited from the global affinity for that type. + ## Supports nodeAffinity, podAffinity and podAntiAffinity. + ## More info: https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/#affinity-and-anti-affinity + ## Example: + ## affinity: + ## nodeAffinity: {} + ## podAffinity: {} + ## podAntiAffinity: {} + affinity: {} ## A subset of PodSecurityContext. PodSecurityContext holds pod-level security attributes and common container settings podSecurityContext: ## A special supplemental group that applies to all containers in a pod. Some volume types allow the Kubelet to @@ -620,6 +655,17 @@ matrixRTC: ## If not set (empty), the pod's priority defaults to the cluster's globalDefault PriorityClass or zero. ## More info: https://kubernetes.io/docs/concepts/scheduling-eviction/pod-priority-preemption/ priorityClassName: "" + ## If specified, the pod's scheduling constraints, configured independently per affinity type. + ## Each affinity type set here overrides only the matching global affinity type for this component. + ## Set an affinity type to {} to blank out the value inherited from the global affinity for that type. + ## Supports nodeAffinity, podAffinity and podAntiAffinity. + ## More info: https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/#affinity-and-anti-affinity + ## Example: + ## affinity: + ## nodeAffinity: {} + ## podAffinity: {} + ## podAntiAffinity: {} + affinity: {} ## A subset of PodSecurityContext. PodSecurityContext holds pod-level security attributes and common container settings podSecurityContext: ## A special supplemental group that applies to all containers in a pod. Some volume types allow the Kubelet to @@ -1002,6 +1048,17 @@ matrixRTC: ## If not set (empty), the pod's priority defaults to the cluster's globalDefault PriorityClass or zero. ## More info: https://kubernetes.io/docs/concepts/scheduling-eviction/pod-priority-preemption/ priorityClassName: "" + ## If specified, the pod's scheduling constraints, configured independently per affinity type. + ## Each affinity type set here overrides only the matching global affinity type for this component. + ## Set an affinity type to {} to blank out the value inherited from the global affinity for that type. + ## Supports nodeAffinity, podAffinity and podAntiAffinity. + ## More info: https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/#affinity-and-anti-affinity + ## Example: + ## affinity: + ## nodeAffinity: {} + ## podAffinity: {} + ## podAntiAffinity: {} + affinity: {} ## A subset of PodSecurityContext. PodSecurityContext holds pod-level security attributes and common container settings podSecurityContext: ## A special supplemental group that applies to all containers in a pod. Some volume types allow the Kubelet to @@ -1265,6 +1322,17 @@ elementAdmin: ## If not set (empty), the pod's priority defaults to the cluster's globalDefault PriorityClass or zero. ## More info: https://kubernetes.io/docs/concepts/scheduling-eviction/pod-priority-preemption/ priorityClassName: "" + ## If specified, the pod's scheduling constraints, configured independently per affinity type. + ## Each affinity type set here overrides only the matching global affinity type for this component. + ## Set an affinity type to {} to blank out the value inherited from the global affinity for that type. + ## Supports nodeAffinity, podAffinity and podAntiAffinity. + ## More info: https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/#affinity-and-anti-affinity + ## Example: + ## affinity: + ## nodeAffinity: {} + ## podAffinity: {} + ## podAntiAffinity: {} + affinity: {} ## A subset of PodSecurityContext. PodSecurityContext holds pod-level security attributes and common container settings podSecurityContext: ## A special supplemental group that applies to all containers in a pod. Some volume types allow the Kubelet to @@ -1532,6 +1600,17 @@ elementWeb: ## If not set (empty), the pod's priority defaults to the cluster's globalDefault PriorityClass or zero. ## More info: https://kubernetes.io/docs/concepts/scheduling-eviction/pod-priority-preemption/ priorityClassName: "" + ## If specified, the pod's scheduling constraints, configured independently per affinity type. + ## Each affinity type set here overrides only the matching global affinity type for this component. + ## Set an affinity type to {} to blank out the value inherited from the global affinity for that type. + ## Supports nodeAffinity, podAffinity and podAntiAffinity. + ## More info: https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/#affinity-and-anti-affinity + ## Example: + ## affinity: + ## nodeAffinity: {} + ## podAffinity: {} + ## podAntiAffinity: {} + affinity: {} ## A subset of PodSecurityContext. PodSecurityContext holds pod-level security attributes and common container settings podSecurityContext: ## A special supplemental group that applies to all containers in a pod. Some volume types allow the Kubelet to @@ -1758,6 +1837,17 @@ haproxy: ## If not set (empty), the pod's priority defaults to the cluster's globalDefault PriorityClass or zero. ## More info: https://kubernetes.io/docs/concepts/scheduling-eviction/pod-priority-preemption/ priorityClassName: "" + ## If specified, the pod's scheduling constraints, configured independently per affinity type. + ## Each affinity type set here overrides only the matching global affinity type for this component. + ## Set an affinity type to {} to blank out the value inherited from the global affinity for that type. + ## Supports nodeAffinity, podAffinity and podAntiAffinity. + ## More info: https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/#affinity-and-anti-affinity + ## Example: + ## affinity: + ## nodeAffinity: {} + ## podAffinity: {} + ## podAntiAffinity: {} + affinity: {} ## A subset of PodSecurityContext. PodSecurityContext holds pod-level security attributes and common container settings podSecurityContext: ## A special supplemental group that applies to all containers in a pod. Some volume types allow the Kubelet to @@ -2131,6 +2221,17 @@ hookshot: ## If not set (empty), the pod's priority defaults to the cluster's globalDefault PriorityClass or zero. ## More info: https://kubernetes.io/docs/concepts/scheduling-eviction/pod-priority-preemption/ priorityClassName: "" + ## If specified, the pod's scheduling constraints, configured independently per affinity type. + ## Each affinity type set here overrides only the matching global affinity type for this component. + ## Set an affinity type to {} to blank out the value inherited from the global affinity for that type. + ## Supports nodeAffinity, podAffinity and podAntiAffinity. + ## More info: https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/#affinity-and-anti-affinity + ## Example: + ## affinity: + ## nodeAffinity: {} + ## podAffinity: {} + ## podAntiAffinity: {} + affinity: {} ## Workload tolerations allows Pods that are part of this (sub)component to 'tolerate' any taint that matches the triple using the matching operator . ## @@ -2535,6 +2636,17 @@ matrixAuthenticationService: ## If not set (empty), the pod's priority defaults to the cluster's globalDefault PriorityClass or zero. ## More info: https://kubernetes.io/docs/concepts/scheduling-eviction/pod-priority-preemption/ priorityClassName: "" + ## If specified, the pod's scheduling constraints, configured independently per affinity type. + ## Each affinity type set here overrides only the matching global affinity type for this component. + ## Set an affinity type to {} to blank out the value inherited from the global affinity for that type. + ## Supports nodeAffinity, podAffinity and podAntiAffinity. + ## More info: https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/#affinity-and-anti-affinity + ## Example: + ## affinity: + ## nodeAffinity: {} + ## podAffinity: {} + ## podAntiAffinity: {} + affinity: {} ## Workload tolerations allows Pods that are part of this (sub)component to 'tolerate' any taint that matches the triple using the matching operator . ## @@ -2811,6 +2923,17 @@ matrixAuthenticationService: ## If not set (empty), the pod's priority defaults to the cluster's globalDefault PriorityClass or zero. ## More info: https://kubernetes.io/docs/concepts/scheduling-eviction/pod-priority-preemption/ priorityClassName: "" + ## If specified, the pod's scheduling constraints, configured independently per affinity type. + ## Each affinity type set here overrides only the matching global affinity type for this component. + ## Set an affinity type to {} to blank out the value inherited from the global affinity for that type. + ## Supports nodeAffinity, podAffinity and podAntiAffinity. + ## More info: https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/#affinity-and-anti-affinity + ## Example: + ## affinity: + ## nodeAffinity: {} + ## podAffinity: {} + ## podAntiAffinity: {} + affinity: {} ## A subset of PodSecurityContext. PodSecurityContext holds pod-level security attributes and common container settings podSecurityContext: ## A special supplemental group that applies to all containers in a pod. Some volume types allow the Kubelet to @@ -3195,6 +3318,17 @@ postgres: ## If not set (empty), the pod's priority defaults to the cluster's globalDefault PriorityClass or zero. ## More info: https://kubernetes.io/docs/concepts/scheduling-eviction/pod-priority-preemption/ priorityClassName: "" + ## If specified, the pod's scheduling constraints, configured independently per affinity type. + ## Each affinity type set here overrides only the matching global affinity type for this component. + ## Set an affinity type to {} to blank out the value inherited from the global affinity for that type. + ## Supports nodeAffinity, podAffinity and podAntiAffinity. + ## More info: https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/#affinity-and-anti-affinity + ## Example: + ## affinity: + ## nodeAffinity: {} + ## podAffinity: {} + ## podAntiAffinity: {} + affinity: {} ## A subset of PodSecurityContext. PodSecurityContext holds pod-level security attributes and common container settings podSecurityContext: ## A special supplemental group that applies to all containers in a pod. Some volume types allow the Kubelet to @@ -3431,6 +3565,17 @@ redis: ## If not set (empty), the pod's priority defaults to the cluster's globalDefault PriorityClass or zero. ## More info: https://kubernetes.io/docs/concepts/scheduling-eviction/pod-priority-preemption/ priorityClassName: "" + ## If specified, the pod's scheduling constraints, configured independently per affinity type. + ## Each affinity type set here overrides only the matching global affinity type for this component. + ## Set an affinity type to {} to blank out the value inherited from the global affinity for that type. + ## Supports nodeAffinity, podAffinity and podAntiAffinity. + ## More info: https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/#affinity-and-anti-affinity + ## Example: + ## affinity: + ## nodeAffinity: {} + ## podAffinity: {} + ## podAntiAffinity: {} + affinity: {} ## A subset of PodSecurityContext. PodSecurityContext holds pod-level security attributes and common container settings podSecurityContext: ## A special supplemental group that applies to all containers in a pod. Some volume types allow the Kubelet to @@ -5361,6 +5506,17 @@ synapse: ## If not set (empty), the pod's priority defaults to the cluster's globalDefault PriorityClass or zero. ## More info: https://kubernetes.io/docs/concepts/scheduling-eviction/pod-priority-preemption/ priorityClassName: "" + ## If specified, the pod's scheduling constraints, configured independently per affinity type. + ## Each affinity type set here overrides only the matching global affinity type for this component. + ## Set an affinity type to {} to blank out the value inherited from the global affinity for that type. + ## Supports nodeAffinity, podAffinity and podAntiAffinity. + ## More info: https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/#affinity-and-anti-affinity + ## Example: + ## affinity: + ## nodeAffinity: {} + ## podAffinity: {} + ## podAntiAffinity: {} + affinity: {} ## A subset of PodSecurityContext. PodSecurityContext holds pod-level security attributes and common container settings podSecurityContext: ## A special supplemental group that applies to all containers in a pod. Some volume types allow the Kubelet to diff --git a/newsfragments/1442.added.md b/newsfragments/1442.added.md new file mode 100644 index 000000000..5e6e692bd --- /dev/null +++ b/newsfragments/1442.added.md @@ -0,0 +1 @@ +Add support for configuring `affinity` (nodeAffinity, podAffinity and podAntiAffinity) on component workloads, along with a global `affinity` applied to all components. Each affinity type is resolved independently: a component that sets one type overrides only that type, inheriting the others from the global affinity, and a type can be blanked out per-component by setting it to `{}`. diff --git a/tests/manifests/__init__.py b/tests/manifests/__init__.py index 8d1b2b848..f60faeb25 100644 --- a/tests/manifests/__init__.py +++ b/tests/manifests/__init__.py @@ -13,6 +13,7 @@ class PropertyType(Enum): AdditionalConfig = "additional" + Affinity = "affinity" Enabled = "enabled" Env = "extraEnv" ExposedServices = "exposedServices" @@ -252,6 +253,7 @@ def __post_init__(self): sidecar_values_file_path_overrides = { # Not possible, will come from the parent component + PropertyType.Affinity: ValuesFilePath.not_supported(), PropertyType.InitContainers: ValuesFilePath.not_supported(), PropertyType.NodeSelector: ValuesFilePath.not_supported(), PropertyType.PodSecurityContext: ValuesFilePath.not_supported(), @@ -381,6 +383,7 @@ def make_synapse_worker_sub_component(worker_name: str, worker_type: str) -> Sub PropertyType.Image: ValuesFilePath.read_elsewhere("synapse", "image"), PropertyType.InitContainers: ValuesFilePath.read_elsewhere("synapse", "extraInitContainers"), PropertyType.Labels: ValuesFilePath.read_elsewhere("synapse", "labels"), + PropertyType.Affinity: ValuesFilePath.read_elsewhere("synapse", "affinity"), PropertyType.NodeSelector: ValuesFilePath.read_elsewhere("synapse", "nodeSelector"), PropertyType.PodSecurityContext: ValuesFilePath.read_elsewhere("synapse", "podSecurityContext"), PropertyType.PriorityClassName: ValuesFilePath.read_elsewhere("synapse", "priorityClassName"), @@ -701,6 +704,7 @@ def make_synapse_worker_sub_component(worker_name: str, worker_type: str) -> Sub PropertyType.InitContainers: ValuesFilePath.read_elsewhere("synapse", "extraInitContainers"), # Job so no livenessProbe PropertyType.LivenessProbe: ValuesFilePath.not_supported(), + PropertyType.Affinity: ValuesFilePath.read_elsewhere("synapse", "affinity"), PropertyType.NodeSelector: ValuesFilePath.read_elsewhere("synapse", "nodeSelector"), PropertyType.PodSecurityContext: ValuesFilePath.read_elsewhere("synapse", "podSecurityContext"), PropertyType.PriorityClassName: ValuesFilePath.read_elsewhere("synapse", "priorityClassName"), diff --git a/tests/manifests/test_pod_affinity.py b/tests/manifests/test_pod_affinity.py new file mode 100644 index 000000000..fffdaf3f6 --- /dev/null +++ b/tests/manifests/test_pod_affinity.py @@ -0,0 +1,196 @@ +# Copyright 2026 Element Creations Ltd +# +# SPDX-License-Identifier: AGPL-3.0-only + +import random +import string + +import pytest +from frozendict import deepfreeze + +from . import DeployableDetails, PropertyType, values_files_to_test +from .utils import ( + iterate_deployables_workload_parts, + template_id, + template_to_deployable_details, +) + +AFFINITY_TYPES = ["nodeAffinity", "podAffinity", "podAntiAffinity"] + + +def _random_value(): + return "".join(random.choices(string.ascii_lowercase, k=8)) + + +def _affinity_for(affinity_type: str, value: str | None = None) -> dict: + """Builds a single-type, schema-valid affinity fragment, e.g. {"nodeAffinity": {...}}.""" + value = value or _random_value() + if affinity_type == "nodeAffinity": + return { + "nodeAffinity": { + "requiredDuringSchedulingIgnoredDuringExecution": { + "nodeSelectorTerms": [ + { + "matchExpressions": [ + { + "key": "k8s.element.io/testing", + "operator": "In", + "values": [value], + } + ] + } + ] + } + } + } + if affinity_type in ("podAffinity", "podAntiAffinity"): + return { + affinity_type: { + "preferredDuringSchedulingIgnoredDuringExecution": [ + { + "weight": 100, + "podAffinityTerm": { + "labelSelector": {"matchLabels": {"k8s.element.io/testing": value}}, + "topologyKey": "kubernetes.io/hostname", + }, + } + ] + } + } + raise ValueError(f"Unknown affinity type {affinity_type}") + + +def _all_affinity_types() -> dict: + """A fragment setting all 3 affinity types, each with a distinct random value.""" + affinity = {} + for affinity_type in AFFINITY_TYPES: + affinity.update(_affinity_for(affinity_type)) + return affinity + + +def _expected_affinity(global_affinity: dict, component_affinity: dict | None) -> dict: + """ + Mirrors the per-type resolution the chart applies: for each affinity type a component that + sets it overrides only that type (an empty value blanks it out); otherwise it is inherited + from the global affinity. + """ + component_affinity = component_affinity or {} + expected = {} + for affinity_type in AFFINITY_TYPES: + if affinity_type in component_affinity: + if component_affinity[affinity_type]: + expected[affinity_type] = component_affinity[affinity_type] + elif global_affinity.get(affinity_type): + expected[affinity_type] = global_affinity[affinity_type] + return expected + + +@pytest.mark.parametrize("values_file", values_files_to_test) +@pytest.mark.asyncio_cooperative +async def test_pod_has_no_affinity_by_default(templates): + for template in templates: + if template["kind"] in ["Deployment", "StatefulSet", "Job"]: + pod_spec = template["spec"]["template"]["spec"] + assert "affinity" not in pod_spec, ( + f"{template_id(template)} has a default affinity when one isn't configured" + ) + + +@pytest.mark.parametrize("affinity_type", AFFINITY_TYPES) +@pytest.mark.parametrize("values_file", values_files_to_test) +@pytest.mark.asyncio_cooperative +async def test_pod_gets_configured_affinity(affinity_type, values, make_templates, release_name): + def set_affinity(deployable_details: DeployableDetails): + deployable_details.set_helm_values(values, PropertyType.Affinity, _affinity_for(affinity_type)) + + iterate_deployables_workload_parts(set_affinity) + for template in await make_templates(values): + if template["kind"] in ["Deployment", "StatefulSet", "Job"]: + pod_spec = template["spec"]["template"]["spec"] + assert "affinity" in pod_spec, f"{template_id(template)} doesn't have an affinity when one is configured" + + deployable_details = template_to_deployable_details(template) + expected_affinity = deployable_details.get_helm_values(values, PropertyType.Affinity) + assert pod_spec["affinity"] == deepfreeze(expected_affinity), ( + f"{template_id(template)} has an unexpected {affinity_type}" + ) + + +@pytest.mark.parametrize("affinity_type", AFFINITY_TYPES) +@pytest.mark.parametrize("values_file", values_files_to_test) +@pytest.mark.asyncio_cooperative +async def test_global_affinity_renders(affinity_type, values, make_templates): + global_affinity = _affinity_for(affinity_type) + values["affinity"] = global_affinity + + for template in await make_templates(values): + if template["kind"] in ["Deployment", "StatefulSet", "Job"]: + pod_spec = template["spec"]["template"]["spec"] + assert pod_spec.get("affinity") == deepfreeze(global_affinity), ( + f"{template_id(template)} doesn't inherit the global {affinity_type}" + ) + + +@pytest.mark.parametrize("affinity_type", AFFINITY_TYPES) +@pytest.mark.parametrize("values_file", values_files_to_test) +@pytest.mark.asyncio_cooperative +async def test_component_override_is_isolated_to_its_type(affinity_type, values, make_templates): + # Global sets all 3 affinity types; a component overrides only one of them. + global_affinity = _all_affinity_types() + values["affinity"] = global_affinity + + component_override = _affinity_for(affinity_type) + + def set_affinity(deployable_details: DeployableDetails): + deployable_details.set_helm_values(values, PropertyType.Affinity, component_override) + + iterate_deployables_workload_parts(set_affinity) + for template in await make_templates(values): + if template["kind"] in ["Deployment", "StatefulSet", "Job"]: + pod_spec = template["spec"]["template"]["spec"] + deployable_details = template_to_deployable_details(template) + component_affinity = deployable_details.get_helm_values(values, PropertyType.Affinity) + expected_affinity = _expected_affinity(global_affinity, component_affinity) + assert pod_spec.get("affinity") == deepfreeze(expected_affinity), ( + f"{template_id(template)} did not isolate the {affinity_type} override to that type" + ) + # The other affinity types must still come from the global affinity. + for other_type in AFFINITY_TYPES: + if other_type == affinity_type: + continue + if component_affinity and other_type in component_affinity: + continue + assert pod_spec["affinity"].get(other_type) == deepfreeze(global_affinity[other_type]), ( + f"{template_id(template)} dropped the global {other_type} when overriding {affinity_type}" + ) + + +@pytest.mark.parametrize("affinity_type", AFFINITY_TYPES) +@pytest.mark.parametrize("values_file", values_files_to_test) +@pytest.mark.asyncio_cooperative +async def test_component_can_blank_global_affinity_type(affinity_type, values, make_templates): + # Global sets all 3 affinity types; a component blanks out just one of them with {}. + global_affinity = _all_affinity_types() + values["affinity"] = global_affinity + + def blank_affinity(deployable_details: DeployableDetails): + deployable_details.set_helm_values(values, PropertyType.Affinity, {affinity_type: {}}) + + iterate_deployables_workload_parts(blank_affinity) + for template in await make_templates(values): + if template["kind"] in ["Deployment", "StatefulSet", "Job"]: + pod_spec = template["spec"]["template"]["spec"] + deployable_details = template_to_deployable_details(template) + component_affinity = deployable_details.get_helm_values(values, PropertyType.Affinity) + affinity = pod_spec.get("affinity", {}) + if component_affinity and component_affinity.get(affinity_type) == {}: + assert affinity_type not in affinity, ( + f"{template_id(template)} did not blank out the global {affinity_type}" + ) + # The non-blanked affinity types must still be inherited from the global affinity. + for other_type in AFFINITY_TYPES: + if other_type == affinity_type: + continue + assert affinity.get(other_type) == deepfreeze(global_affinity[other_type]), ( + f"{template_id(template)} dropped the global {other_type} when blanking {affinity_type}" + )