Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions helm/kmcp/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,33 @@ The following table lists the configurable parameters of the KMCP chart and thei
| `securityContext.allowPrivilegeEscalation` | Allow privilege escalation | `false` |
| `securityContext.capabilities.drop` | Capabilities to drop | `["ALL"]` |

### Pod Disruption Budget

Creates a `PodDisruptionBudget` for the controller pods so that voluntary disruptions (node drains, cluster upgrades) keep a minimum number of replicas running. Disabled by default.

| Parameter | Description | Default |
|-----------|-------------|---------|
| `podDisruptionBudget.enabled` | Create a PodDisruptionBudget | `false` |
| `podDisruptionBudget.minAvailable` | Minimum available pods, as an integer or percentage string | `1` |
| `podDisruptionBudget.maxUnavailable` | Maximum unavailable pods, as an integer or percentage string | `""` |
| `podDisruptionBudget.unhealthyPodEvictionPolicy` | `IfHealthyBudget` or `AlwaysAllow` | `""` (omitted) |
| `podDisruptionBudget.annotations` | Extra annotations for the PodDisruptionBudget | `{}` |
| `podDisruptionBudget.labels` | Extra labels for the PodDisruptionBudget | `{}` |

Set exactly one of `minAvailable` and `maxUnavailable`; the chart rejects a configuration that sets both or neither.

```bash
helm install kmcp kmcp/kmcp \
--set controller.replicaCount=2 \
--set podDisruptionBudget.enabled=true \
--set podDisruptionBudget.minAvailable=1 \
--set podDisruptionBudget.maxUnavailable=""
```

**Note**: `minAvailable: 1` together with the default `controller.replicaCount: 1` blocks node drains, because evicting the only replica would breach the budget. Raise `controller.replicaCount` to at least 2 before enabling the budget. Leader election keeps a single controller active across the replicas.

**Note**: The `policy/v1` API requires Kubernetes 1.21+, and `unhealthyPodEvictionPolicy` requires 1.27+ (beta) or 1.31+ (stable).

### Service Configuration

| Parameter | Description | Default |
Expand Down
32 changes: 32 additions & 0 deletions helm/kmcp/templates/_helpers.tpl
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,38 @@ Guards on the rbac block
{{- end -}}
{{- end -}}

{{/*
Report whether a PodDisruptionBudget field is set.
Outputs "true" when the value is neither nil nor an empty string, otherwise the
empty string. The integer 0 counts as set, so it is not silently dropped.
Usage: {{ eq (include "kmcp.pdb.isSet" .Values.podDisruptionBudget.minAvailable) "true" }}
*/}}
{{- define "kmcp.pdb.isSet" -}}
{{- if and (not (kindIs "invalid" .)) (ne (toString .) "") -}}
true
{{- end -}}
{{- end -}}

{{/*
Guards on the podDisruptionBudget block
*/}}
{{- define "kmcp.pdb.validate" -}}
{{- $pdb := .Values.podDisruptionBudget -}}
{{- $hasMin := eq (include "kmcp.pdb.isSet" $pdb.minAvailable) "true" -}}
{{- $hasMax := eq (include "kmcp.pdb.isSet" $pdb.maxUnavailable) "true" -}}
{{- if and $hasMin $hasMax -}}
{{- fail "podDisruptionBudget.minAvailable and podDisruptionBudget.maxUnavailable are mutually exclusive. Set exactly one of them." -}}
{{- end -}}
{{- if and (not $hasMin) (not $hasMax) -}}
{{- fail "podDisruptionBudget.enabled is true but neither podDisruptionBudget.minAvailable nor podDisruptionBudget.maxUnavailable is set. Set exactly one of them." -}}
{{- end -}}
{{- if eq (include "kmcp.pdb.isSet" $pdb.unhealthyPodEvictionPolicy) "true" -}}
{{- if not (has $pdb.unhealthyPodEvictionPolicy (list "AlwaysAllow" "IfHealthyBudget")) -}}
{{- fail (printf "podDisruptionBudget.unhealthyPodEvictionPolicy must be either \"AlwaysAllow\" or \"IfHealthyBudget\", got %q" (toString $pdb.unhealthyPodEvictionPolicy)) -}}
{{- end -}}
{{- end -}}
{{- end -}}

{{/*
Create controller manager container args
*/}}
Expand Down
30 changes: 30 additions & 0 deletions helm/kmcp/templates/poddisruptionbudget.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
{{- if .Values.podDisruptionBudget.enabled }}
{{- include "kmcp.pdb.validate" . }}
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: {{ include "kmcp.fullname" . }}-controller-manager
namespace: {{ include "kmcp.namespace" . }}
labels:
{{- include "kmcp.labels" . | nindent 4 }}
{{- with .Values.podDisruptionBudget.labels }}
{{- toYaml . | nindent 4 }}
{{- end }}
{{- with .Values.podDisruptionBudget.annotations }}
annotations:
{{- toYaml . | nindent 4 }}
{{- end }}
spec:
{{- if eq (include "kmcp.pdb.isSet" .Values.podDisruptionBudget.minAvailable) "true" }}
minAvailable: {{ .Values.podDisruptionBudget.minAvailable }}
{{- end }}
{{- if eq (include "kmcp.pdb.isSet" .Values.podDisruptionBudget.maxUnavailable) "true" }}
maxUnavailable: {{ .Values.podDisruptionBudget.maxUnavailable }}
{{- end }}
{{- if eq (include "kmcp.pdb.isSet" .Values.podDisruptionBudget.unhealthyPodEvictionPolicy) "true" }}
unhealthyPodEvictionPolicy: {{ .Values.podDisruptionBudget.unhealthyPodEvictionPolicy }}
{{- end }}
selector:
matchLabels:
{{- include "kmcp.selectorLabels" . | nindent 6 }}
{{- end }}
104 changes: 104 additions & 0 deletions helm/kmcp/tests/__snapshot__/poddisruptionbudget_test.yaml.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
should accept a percentage for minAvailable:
1: |
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
labels:
app.kubernetes.io/instance: RELEASE-NAME
app.kubernetes.io/managed-by: Helm
app.kubernetes.io/name: kmcp
control-plane: controller-manager
helm.sh/chart: kmcp-1.0.0
name: RELEASE-NAME-controller-manager
namespace: NAMESPACE
spec:
minAvailable: 50%
selector:
matchLabels:
app.kubernetes.io/instance: RELEASE-NAME
app.kubernetes.io/name: kmcp
control-plane: controller-manager
should create a pdb with maxUnavailable when enabled:
1: |
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
labels:
app.kubernetes.io/instance: RELEASE-NAME
app.kubernetes.io/managed-by: Helm
app.kubernetes.io/name: kmcp
control-plane: controller-manager
helm.sh/chart: kmcp-1.0.0
name: RELEASE-NAME-controller-manager
namespace: NAMESPACE
spec:
maxUnavailable: 1
selector:
matchLabels:
app.kubernetes.io/instance: RELEASE-NAME
app.kubernetes.io/name: kmcp
control-plane: controller-manager
should create a pdb with minAvailable when enabled:
1: |
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
labels:
app.kubernetes.io/instance: RELEASE-NAME
app.kubernetes.io/managed-by: Helm
app.kubernetes.io/name: kmcp
control-plane: controller-manager
helm.sh/chart: kmcp-1.0.0
name: RELEASE-NAME-controller-manager
namespace: NAMESPACE
spec:
minAvailable: 1
selector:
matchLabels:
app.kubernetes.io/instance: RELEASE-NAME
app.kubernetes.io/name: kmcp
control-plane: controller-manager
should render custom labels and annotations:
1: |
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
annotations:
custom-annotation: annotation-value
labels:
app.kubernetes.io/instance: RELEASE-NAME
app.kubernetes.io/managed-by: Helm
app.kubernetes.io/name: kmcp
control-plane: controller-manager
custom-label: custom-value
helm.sh/chart: kmcp-1.0.0
name: RELEASE-NAME-controller-manager
namespace: NAMESPACE
spec:
minAvailable: 1
selector:
matchLabels:
app.kubernetes.io/instance: RELEASE-NAME
app.kubernetes.io/name: kmcp
control-plane: controller-manager
should render unhealthyPodEvictionPolicy when set:
1: |
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
labels:
app.kubernetes.io/instance: RELEASE-NAME
app.kubernetes.io/managed-by: Helm
app.kubernetes.io/name: kmcp
control-plane: controller-manager
helm.sh/chart: kmcp-1.0.0
name: RELEASE-NAME-controller-manager
namespace: NAMESPACE
spec:
minAvailable: 1
selector:
matchLabels:
app.kubernetes.io/instance: RELEASE-NAME
app.kubernetes.io/name: kmcp
control-plane: controller-manager
unhealthyPodEvictionPolicy: AlwaysAllow
167 changes: 167 additions & 0 deletions helm/kmcp/tests/poddisruptionbudget_test.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
suite: Test poddisruptionbudget template
templates:
- poddisruptionbudget.yaml

tests:
- it: should not create a pdb by default
template: poddisruptionbudget.yaml
asserts:
- hasDocuments:
count: 0

- it: should not create a pdb when disabled
template: poddisruptionbudget.yaml
set:
podDisruptionBudget.enabled: false
asserts:
- hasDocuments:
count: 0

- it: should create a pdb with minAvailable when enabled
template: poddisruptionbudget.yaml
set:
podDisruptionBudget.enabled: true
podDisruptionBudget.minAvailable: 1
podDisruptionBudget.maxUnavailable: ""
asserts:
- hasDocuments:
count: 1
- isKind:
of: PodDisruptionBudget
- isAPIVersion:
of: policy/v1
- equal:
path: spec.minAvailable
value: 1
- notExists:
path: spec.maxUnavailable
- matchSnapshot: {}

- it: should accept a percentage for minAvailable
template: poddisruptionbudget.yaml
set:
podDisruptionBudget.enabled: true
podDisruptionBudget.minAvailable: 50%
podDisruptionBudget.maxUnavailable: ""
asserts:
- hasDocuments:
count: 1
- equal:
path: spec.minAvailable
value: 50%
- matchSnapshot: {}

- it: should create a pdb with maxUnavailable when enabled
template: poddisruptionbudget.yaml
set:
podDisruptionBudget.enabled: true
podDisruptionBudget.minAvailable: ""
podDisruptionBudget.maxUnavailable: 1
asserts:
- hasDocuments:
count: 1
- equal:
path: spec.maxUnavailable
value: 1
- notExists:
path: spec.minAvailable
- matchSnapshot: {}

- it: should select the controller manager pods
template: poddisruptionbudget.yaml
set:
podDisruptionBudget.enabled: true
podDisruptionBudget.minAvailable: 1
podDisruptionBudget.maxUnavailable: ""
asserts:
- equal:
path: spec.selector.matchLabels["control-plane"]
value: controller-manager
- equal:
path: spec.selector.matchLabels["app.kubernetes.io/name"]
value: kmcp

- it: should not render unhealthyPodEvictionPolicy by default
template: poddisruptionbudget.yaml
set:
podDisruptionBudget.enabled: true
podDisruptionBudget.minAvailable: 1
podDisruptionBudget.maxUnavailable: ""
asserts:
- notExists:
path: spec.unhealthyPodEvictionPolicy

- it: should render unhealthyPodEvictionPolicy when set
template: poddisruptionbudget.yaml
set:
podDisruptionBudget.enabled: true
podDisruptionBudget.minAvailable: 1
podDisruptionBudget.maxUnavailable: ""
podDisruptionBudget.unhealthyPodEvictionPolicy: AlwaysAllow
asserts:
- equal:
path: spec.unhealthyPodEvictionPolicy
value: AlwaysAllow
- matchSnapshot: {}

- it: should render custom labels and annotations
template: poddisruptionbudget.yaml
set:
podDisruptionBudget.enabled: true
podDisruptionBudget.minAvailable: 1
podDisruptionBudget.maxUnavailable: ""
podDisruptionBudget.labels:
custom-label: custom-value
podDisruptionBudget.annotations:
custom-annotation: annotation-value
asserts:
- equal:
path: metadata.labels["custom-label"]
value: custom-value
- equal:
path: metadata.annotations["custom-annotation"]
value: annotation-value
- matchSnapshot: {}

- it: should use the release namespace override
template: poddisruptionbudget.yaml
set:
podDisruptionBudget.enabled: true
podDisruptionBudget.minAvailable: 1
podDisruptionBudget.maxUnavailable: ""
namespaceOverride: custom-namespace
asserts:
- equal:
path: metadata.namespace
value: custom-namespace

- it: should fail when both minAvailable and maxUnavailable are set
template: poddisruptionbudget.yaml
set:
podDisruptionBudget.enabled: true
podDisruptionBudget.minAvailable: 1
podDisruptionBudget.maxUnavailable: 1
asserts:
- failedTemplate:
errorMessage: podDisruptionBudget.minAvailable and podDisruptionBudget.maxUnavailable are mutually exclusive. Set exactly one of them.

- it: should fail when neither minAvailable nor maxUnavailable is set
template: poddisruptionbudget.yaml
set:
podDisruptionBudget.enabled: true
podDisruptionBudget.minAvailable: ""
podDisruptionBudget.maxUnavailable: ""
asserts:
- failedTemplate:
errorMessage: podDisruptionBudget.enabled is true but neither podDisruptionBudget.minAvailable nor podDisruptionBudget.maxUnavailable is set. Set exactly one of them.

- it: should fail on an invalid unhealthyPodEvictionPolicy
template: poddisruptionbudget.yaml
set:
podDisruptionBudget.enabled: true
podDisruptionBudget.minAvailable: 1
podDisruptionBudget.maxUnavailable: ""
podDisruptionBudget.unhealthyPodEvictionPolicy: Whenever
asserts:
- failedTemplate:
errorMessage: podDisruptionBudget.unhealthyPodEvictionPolicy must be either "AlwaysAllow" or "IfHealthyBudget", got "Whenever"
Loading