Skip to content

Instantly share code, notes, and snippets.

@iyalang
Created June 28, 2024 08:04
Show Gist options
  • Save iyalang/014d5364b37f09d4ca5ee0507fd4b267 to your computer and use it in GitHub Desktop.
Save iyalang/014d5364b37f09d4ca5ee0507fd4b267 to your computer and use it in GitHub Desktop.
Kyverno policy for validating Pod Disruption Budgets (PDBs)
---
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: pdb-validation
annotations:
policies.kyverno.io/title: Check PodDisruptionBudget minAvailable and maxUnavailable
policies.kyverno.io/category: Other
policies.kyverno.io/subject: PodDisruptionBudget, Deployment, StatefulSet
policies.kyverno.io/description: >-
This policy ensures that all PodDisruptionBudgets (PDBs) are configured to allow for some disruptions.
spec:
validationFailureAction: Enforce
background: false
rules:
# Check if maxUnavailable is > 0
- name: pdb-maxunavailable
match:
any:
- resources:
kinds:
- PodDisruptionBudget
preconditions:
any:
- key: '{{request.operation}}'
operator: NotEquals
value: DELETE
validate:
message: "The value of maxUnavailable must be greater than zero."
deny:
conditions:
any:
- key: '{{ request.object.spec.maxUnavailable || "null" }}'
operator: Equals
value: 0
- key: '{{ request.object.spec.maxUnavailable || "null" }}'
operator: Equals
value: "0%"
# Check if minAvailable != 100%
- name: pdb-minavailable-100-percent
match:
any:
- resources:
kinds:
- PodDisruptionBudget
preconditions:
any:
- key: '{{request.operation}}'
operator: NotEquals
value: DELETE
validate:
message: "minAvailable should not be 100% since that prevents all disruptions."
deny:
conditions:
any:
- key: '{{ request.object.spec.minAvailable || "none" }}'
operator: Equals
value: "100%"
# Check if minAvailable != the number of Deployment replicas
- name: pdb-minavailable-deployments
match:
any:
- resources:
kinds:
- PodDisruptionBudget
preconditions:
any:
- key: '{{request.operation}}'
operator: NotEquals
value: DELETE
context:
- name: replicasDeployment
apiCall:
urlPath: '/apis/apps/v1/namespaces/{{request.namespace}}/deployments'
# This "none":"none" map is added to labels of deployments that have zero labels,
# else the rule would fail with "failed to load context: Invalid type"
jmesPath: 'items[?label_match(`{{request.object.spec.selector.matchLabels}}`, spec.template.metadata.labels || {"none":"none"})] | [0] | metadata.annotations."downscaler/original-replicas" || spec.replicas || "null"'
- name: deploymentName
apiCall:
urlPath: '/apis/apps/v1/namespaces/{{request.namespace}}/deployments'
jmesPath: 'items[?label_match(`{{request.object.spec.selector.matchLabels}}`, spec.template.metadata.labels || {"none":"none"})] | [0] | metadata.name || "null"'
- name: hpaCount
apiCall:
urlPath: '/apis/autoscaling/v2/namespaces/{{request.namespace}}/horizontalpodautoscalers'
jmesPath: items[?spec.scaleTargetRef.name=='{{deploymentName}}'] | length(@)
validate:
message: >-
The minAvailable value of the PDB should be lower than the number of replicas of the Deployment or StatefulSet.
If your Deployment/StatefulSet has only one replica, please don't create any PDBs for it.
deny:
conditions:
all:
- key: '{{ request.object.spec.minAvailable || "0" }}'
operator: GreaterThanOrEquals
value: '{{ replicasDeployment }}'
# Not checking min replicas if there is an HPA because replicas are set there
- key: '{{ hpaCount }}'
operator: Equals
value: 0
# Check if minAvailable != the number of StatefulSet replicas
- name: pdb-minavailable-statefulsets
match:
any:
- resources:
kinds:
- PodDisruptionBudget
preconditions:
any:
- key: '{{request.operation}}'
operator: NotEquals
value: DELETE
context:
- name: replicasStatefulSet
apiCall:
urlPath: '/apis/apps/v1/namespaces/{{request.namespace}}/statefulsets'
jmesPath: 'items[?label_match(`{{request.object.spec.selector.matchLabels}}`, spec.template.metadata.labels || {"none":"none"})] | [0] | metadata.annotations."downscaler/original-replicas" || spec.replicas || "null"'
- name: statefulSetName
apiCall:
urlPath: '/apis/apps/v1/namespaces/{{request.namespace}}/statefulsets'
jmesPath: 'items[?label_match(`{{request.object.spec.selector.matchLabels}}`, spec.template.metadata.labels || {"none":"none"})] | [0] | metadata.name || "null"'
- name: hpaCount
apiCall:
urlPath: '/apis/autoscaling/v2/namespaces/{{request.namespace}}/horizontalpodautoscalers'
jmesPath: items[?spec.scaleTargetRef.name=='{{statefulSetName}}'] | length(@)
validate:
message: >-
The minAvailable value of the PDB should be lower than the number of replicas of the Deployment or StatefulSet.
If your Deployment/StatefulSet has only one replica, please don't create any PDBs for it.
deny:
conditions:
all:
- key: '{{ request.object.spec.minAvailable || "0" }}'
operator: GreaterThanOrEquals
value: '{{ replicasStatefulSet }}'
- key: '{{ hpaCount }}'
operator: Equals
value: 0
# Check if Deployment has more than 1 replica
- name: pdb-minavailable-deployments-one-replica
match:
any:
- resources:
kinds:
- PodDisruptionBudget
preconditions:
any:
- key: '{{request.operation}}'
operator: NotEquals
value: DELETE
context:
- name: replicasDeployment
apiCall:
urlPath: '/apis/apps/v1/namespaces/{{request.namespace}}/deployments'
jmesPath: 'items[?label_match(`{{request.object.spec.selector.matchLabels}}`, spec.template.metadata.labels || {"none":"none"})] | [0] | metadata.annotations."downscaler/original-replicas" || spec.replicas || "null"'
- name: deploymentName
apiCall:
urlPath: '/apis/apps/v1/namespaces/{{request.namespace}}/deployments'
jmesPath: 'items[?label_match(`{{request.object.spec.selector.matchLabels}}`, spec.template.metadata.labels || {"none":"none"})] | [0] | metadata.name || "null"'
- name: hpaCount
apiCall:
urlPath: '/apis/autoscaling/v2/namespaces/{{request.namespace}}/horizontalpodautoscalers'
jmesPath: items[?spec.scaleTargetRef.name=='{{deploymentName}}'] | length(@)
validate:
message: >-
Deployments or StatefulSets that have only 1 replica are not allowed to have PDBs since this would prevent any disruptions.
deny:
conditions:
all:
- key: '{{ replicasDeployment }}'
operator: Equals
value: 1
- key: '{{ hpaCount }}'
operator: Equals
value: 0
# Check if StatefulSet has more than 1 replica
- name: pdb-minavailable-statefulsets-one-replica
match:
any:
- resources:
kinds:
- PodDisruptionBudget
preconditions:
any:
- key: '{{request.operation}}'
operator: NotEquals
value: DELETE
context:
- name: replicasStatefulSet
apiCall:
urlPath: '/apis/apps/v1/namespaces/{{request.namespace}}/statefulsets'
jmesPath: 'items[?label_match(`{{request.object.spec.selector.matchLabels}}`, spec.template.metadata.labels || {"none":"none"})] | [0] | metadata.annotations."downscaler/original-replicas" || spec.replicas || "null"'
- name: statefulSetName
apiCall:
urlPath: '/apis/apps/v1/namespaces/{{request.namespace}}/statefulsets'
jmesPath: 'items[?label_match(`{{request.object.spec.selector.matchLabels}}`, spec.template.metadata.labels || {"none":"none"})] | [0] | metadata.name || "null"'
- name: hpaCount
apiCall:
urlPath: '/apis/autoscaling/v2/namespaces/{{request.namespace}}/horizontalpodautoscalers'
jmesPath: items[?spec.scaleTargetRef.name=='{{statefulSetName}}'] | length(@)
validate:
message: >-
Deployments or StatefulSets that have only 1 replica are not allowed to have PDBs since this would prevent any disruptions.
deny:
conditions:
all:
- key: '{{ replicasStatefulSet }}'
operator: Equals
value: 1
- key: '{{ hpaCount }}'
operator: Equals
value: 0
# Check if minAvailable != minReplicas in the HPA (if it exists)
- name: pdb-minavailable-hpa-deployments
match:
any:
- resources:
kinds:
- PodDisruptionBudget
preconditions:
any:
- key: '{{request.operation}}'
operator: NotEquals
value: DELETE
context:
- name: deploymentName
apiCall:
urlPath: '/apis/apps/v1/namespaces/{{request.namespace}}/deployments'
jmesPath: 'items[?label_match(`{{request.object.spec.selector.matchLabels}}`, spec.template.metadata.labels || {"none":"none"})] | [0] | metadata.name || "null"'
- name: hpaMinReplicasDeployment
apiCall:
urlPath: '/apis/autoscaling/v2/namespaces/{{request.namespace}}/horizontalpodautoscalers'
jmesPath: items[?spec.scaleTargetRef.name=='{{deploymentName}}'] | [0] | metadata.annotations."downscaler/original-replicas" || spec.minReplicas
validate:
message: >-
The HPA for the corresponding Deployment or StatefulSet has its minReplicas equal to the minAvailable value of the PDB
which is not permitted. If minReplicas of the HPA equals 1, please don't create any PDBs.
deny:
conditions:
any:
- key: '{{ request.object.spec.minAvailable || "0" }}'
operator: GreaterThanOrEquals
value: '{{ hpaMinReplicasDeployment }}'
# Check if minAvailable != minReplicas in the HPA (if it exists)
- name: pdb-minavailable-hpa-statefulsets
match:
any:
- resources:
kinds:
- PodDisruptionBudget
preconditions:
any:
- key: '{{request.operation}}'
operator: NotEquals
value: DELETE
context:
- name: statefulSetName
apiCall:
urlPath: '/apis/apps/v1/namespaces/{{request.namespace}}/statefulsets'
jmesPath: 'items[?label_match(`{{request.object.spec.selector.matchLabels}}`, spec.template.metadata.labels || {"none":"none"})] | [0] | metadata.name || "null"'
- name: hpaMinReplicasStatefulSet
apiCall:
urlPath: '/apis/autoscaling/v2/namespaces/{{request.namespace}}/horizontalpodautoscalers'
jmesPath: items[?spec.scaleTargetRef.name=='{{statefulSetName}}'] | [0] | metadata.annotations."downscaler/original-replicas" || spec.minReplicas || "null"
validate:
message: >-
The HPA for the corresponding Deployment or StatefulSet has its minReplicas equal to the minAvailable value of the PDB
which is not permitted. If minReplicas of the HPA equals 1, please don't create any PDBs.
deny:
conditions:
any:
- key: '{{ request.object.spec.minAvailable || "0" }}'
operator: GreaterThanOrEquals
value: '{{ hpaMinReplicasStatefulSet }}'
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment