117 lines
4.6 KiB
YAML
117 lines
4.6 KiB
YAML
apiVersion: kyverno.io/v1
|
|
kind: Policy
|
|
metadata:
|
|
name: enforce-topology-spread
|
|
namespace: rossum
|
|
annotations:
|
|
policies.kyverno.io/title: Spread pods based on zone topology
|
|
policies.kyverno.io/subject: Pod
|
|
spec:
|
|
# Depending on the desired outcome for the applications, there could be multiple solutions
|
|
# to the problem, naturally, the most complex and difficult was chosen in order to:
|
|
# 1. Preserve existing topology spread if defined
|
|
# 2. Inject topology spread if not defined
|
|
# 3. Override any settings of topology spread based on zones
|
|
#
|
|
# see https://github.com/kyverno/kyverno/issues/10655 for details why it cannot
|
|
# be achieved using the merge patch
|
|
rules:
|
|
# Check if existing zone topology spread is defined and overwrite it
|
|
- name: enforce-zone-topology-spread-configuration
|
|
match:
|
|
any:
|
|
- resources:
|
|
kinds:
|
|
- Deployment
|
|
- StatefulSet
|
|
selector:
|
|
matchLabels:
|
|
# Additional validation policies would be needed to ensure the label is present on every resource
|
|
app.kubernetes.io/name: "*"
|
|
operations:
|
|
- CREATE
|
|
- UPDATE
|
|
mutate:
|
|
foreach:
|
|
- list: "request.object.spec.template.spec.topologySpreadConstraints || []"
|
|
# Use precondition to mutate
|
|
preconditions:
|
|
any:
|
|
- key: "{{ element.topologyKey }}"
|
|
operator: Equals
|
|
value: topology.kubernetes.io/zone
|
|
patchesJson6902: |-
|
|
- path: /spec/template/spec/topologySpreadConstraints/{{elementIndex}}
|
|
op: replace
|
|
value:
|
|
maxSkew: 1
|
|
topologyKey: topology.kubernetes.io/zone
|
|
whenUnsatisfiable: ScheduleAnyway
|
|
labelSelector:
|
|
matchLabels:
|
|
app.kubernetes.io/name: '{{request.object.spec.selector.matchLabels."app.kubernetes.io/name"}}'
|
|
# Check if the zone topology spread is not defined and inject it
|
|
- name: inject-zone-topology-spread
|
|
match:
|
|
any:
|
|
- resources:
|
|
kinds:
|
|
- Deployment
|
|
- StatefulSet
|
|
selector:
|
|
matchLabels:
|
|
# Additional validation policies would be needed to ensure the label is present on every resource
|
|
app.kubernetes.io/name: "*"
|
|
operations:
|
|
- CREATE
|
|
- UPDATE
|
|
mutate:
|
|
foreach:
|
|
- list: "request.object.spec.template.spec.topologySpreadConstraints || []"
|
|
# Use precondition to mutate
|
|
preconditions:
|
|
any:
|
|
- key: "{{ request.object.spec.template.spec.topologySpreadConstraints[].topologyKey }}"
|
|
operator: AllNotIn
|
|
value:
|
|
- topology.kubernetes.io/zone
|
|
patchesJson6902: |-
|
|
- path: /spec/template/spec/topologySpreadConstraints/0
|
|
op: add
|
|
value:
|
|
maxSkew: 1
|
|
topologyKey: topology.kubernetes.io/zone
|
|
whenUnsatisfiable: ScheduleAnyway
|
|
labelSelector:
|
|
matchLabels:
|
|
app.kubernetes.io/name: '{{request.object.spec.selector.matchLabels."app.kubernetes.io/name"}}'
|
|
# Create topology spread if it does not exist
|
|
- name: create-topology-spread
|
|
match:
|
|
any:
|
|
- resources:
|
|
kinds:
|
|
- Deployment
|
|
- StatefulSet
|
|
selector:
|
|
matchLabels:
|
|
# Additional validation policies would be needed to ensure the label is present on every resource
|
|
app.kubernetes.io/name: "*"
|
|
operations:
|
|
- CREATE
|
|
- UPDATE
|
|
mutate:
|
|
patchStrategicMerge:
|
|
spec:
|
|
template:
|
|
spec:
|
|
# Ensure the zone topology spread is present if undefined
|
|
+(topologySpreadConstraints):
|
|
- maxSkew: 1
|
|
topologyKey: topology.kubernetes.io/zone
|
|
# Depending on the workload and requirements, ScheduleAnyway or DoNotSchedule might be chosen
|
|
whenUnsatisfiable: ScheduleAnyway
|
|
labelSelector:
|
|
matchLabels:
|
|
app.kubernetes.io/name: '{{request.object.spec.selector.matchLabels."app.kubernetes.io/name"}}'
|