diff --git a/helm/templates/NOTES.txt b/helm/templates/NOTES.txt index 7a2dfd2dd2..e47360e6cf 100644 --- a/helm/templates/NOTES.txt +++ b/helm/templates/NOTES.txt @@ -22,3 +22,4 @@ APP VERSION: {{ .Chart.AppVersion }} {{ include "fluss.security.validateValues" . }} {{ include "fluss.metrics.validateValues" . }} +{{ include "fluss.pdb.validateValues" . }} diff --git a/helm/templates/_helpers.tpl b/helm/templates/_helpers.tpl index 5fe371e0cd..3933debca6 100644 --- a/helm/templates/_helpers.tpl +++ b/helm/templates/_helpers.tpl @@ -100,3 +100,34 @@ imagePullSecrets: {{- end }} {{- end }} +{{/* + Validate PodDisruptionBudget for a given component. + Usage: include "fluss.pdb.validate" (dict "component" "tablet" "pdb" .Values.tablet.podDisruptionBudget) +*/}} +{{- define "fluss.pdb.validate" -}} +{{- if .pdb.enabled -}} + {{- $hasMin := hasKey .pdb "minAvailable" -}} + {{- $hasMax := hasKey .pdb "maxUnavailable" -}} + {{- if and $hasMin $hasMax -}} + {{- printf "%s.podDisruptionBudget: cannot set both minAvailable and maxUnavailable" .component -}} + {{- else if not (or $hasMin $hasMax) -}} + {{- printf "%s.podDisruptionBudget: must set either minAvailable or maxUnavailable when enabled" .component -}} + {{- end -}} +{{- end -}} +{{- end -}} + +{{/* + Aggregate PDB validation for all components. + include "fluss.pdb.validateValues" . +*/}} +{{- define "fluss.pdb.validateValues" -}} +{{- $errMessages := list -}} +{{- $errMessages = append $errMessages (include "fluss.pdb.validate" (dict "component" "tablet" "pdb" .Values.tablet.podDisruptionBudget)) -}} +{{- $errMessages = append $errMessages (include "fluss.pdb.validate" (dict "component" "coordinator" "pdb" .Values.coordinator.podDisruptionBudget)) -}} +{{- $errMessages = without $errMessages "" -}} +{{- $errMessage := join "\n" $errMessages -}} +{{- if $errMessage -}} +{{- printf "\nPDB VALIDATION:\n%s" $errMessage | fail -}} +{{- end -}} +{{- end -}} + diff --git a/helm/templates/pdb-coordinator.yaml b/helm/templates/pdb-coordinator.yaml new file mode 100644 index 0000000000..79e03f3451 --- /dev/null +++ b/helm/templates/pdb-coordinator.yaml @@ -0,0 +1,37 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +{{- if .Values.coordinator.podDisruptionBudget.enabled }} +apiVersion: policy/v1 +kind: PodDisruptionBudget +metadata: + name: coordinator-server + labels: + {{- include "fluss.labels" . | nindent 4 }} +spec: + {{- if hasKey .Values.coordinator.podDisruptionBudget "minAvailable" }} + minAvailable: {{ .Values.coordinator.podDisruptionBudget.minAvailable }} + {{- end }} + {{- if hasKey .Values.coordinator.podDisruptionBudget "maxUnavailable" }} + maxUnavailable: {{ .Values.coordinator.podDisruptionBudget.maxUnavailable }} + {{- end }} + selector: + matchLabels: + {{- include "fluss.selectorLabels" . | nindent 6 }} + app.kubernetes.io/component: coordinator +{{- end }} diff --git a/helm/templates/pdb-tablet.yaml b/helm/templates/pdb-tablet.yaml new file mode 100644 index 0000000000..ce7641efdb --- /dev/null +++ b/helm/templates/pdb-tablet.yaml @@ -0,0 +1,37 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +{{- if .Values.tablet.podDisruptionBudget.enabled }} +apiVersion: policy/v1 +kind: PodDisruptionBudget +metadata: + name: tablet-server + labels: + {{- include "fluss.labels" . | nindent 4 }} +spec: + {{- if hasKey .Values.tablet.podDisruptionBudget "minAvailable" }} + minAvailable: {{ .Values.tablet.podDisruptionBudget.minAvailable }} + {{- end }} + {{- if hasKey .Values.tablet.podDisruptionBudget "maxUnavailable" }} + maxUnavailable: {{ .Values.tablet.podDisruptionBudget.maxUnavailable }} + {{- end }} + selector: + matchLabels: + {{- include "fluss.selectorLabels" . | nindent 6 }} + app.kubernetes.io/component: tablet +{{- end }} diff --git a/helm/templates/sts-coordinator.yaml b/helm/templates/sts-coordinator.yaml index fbdd36712b..1f5d046a0c 100644 --- a/helm/templates/sts-coordinator.yaml +++ b/helm/templates/sts-coordinator.yaml @@ -32,8 +32,15 @@ spec: template: metadata: labels: + {{- with .Values.coordinator.podLabels }} + {{- toYaml . | nindent 8 }} + {{- end }} {{- include "fluss.selectorLabels" . | nindent 8 }} app.kubernetes.io/component: coordinator + {{- with .Values.coordinator.podAnnotations }} + annotations: + {{- toYaml . | nindent 8 }} + {{- end }} spec: {{- if .Values.serviceAccount.create }} serviceAccountName: {{ .Values.serviceAccount.name | default (include "fluss.fullname" .) }} diff --git a/helm/templates/sts-tablet.yaml b/helm/templates/sts-tablet.yaml index bb8be8b57c..94710561e0 100644 --- a/helm/templates/sts-tablet.yaml +++ b/helm/templates/sts-tablet.yaml @@ -32,8 +32,15 @@ spec: template: metadata: labels: + {{- with .Values.tablet.podLabels }} + {{- toYaml . | nindent 8 }} + {{- end }} {{- include "fluss.selectorLabels" . | nindent 8 }} app.kubernetes.io/component: tablet + {{- with .Values.tablet.podAnnotations }} + annotations: + {{- toYaml . | nindent 8 }} + {{- end }} spec: {{- if .Values.serviceAccount.create }} serviceAccountName: {{ .Values.serviceAccount.name | default (include "fluss.fullname" .) }} diff --git a/helm/tests/pod_metadata_and_pdb_test.yaml b/helm/tests/pod_metadata_and_pdb_test.yaml new file mode 100644 index 0000000000..53527568f2 --- /dev/null +++ b/helm/tests/pod_metadata_and_pdb_test.yaml @@ -0,0 +1,285 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +suite: pod-metadata-defaults +templates: + - templates/sts-tablet.yaml + - templates/sts-coordinator.yaml + +tests: + - it: should not render pod annotations by default on tablet + asserts: + - isNull: + path: spec.template.metadata.annotations + template: templates/sts-tablet.yaml + + - it: should not render pod annotations by default on coordinator + asserts: + - isNull: + path: spec.template.metadata.annotations + template: templates/sts-coordinator.yaml + +--- + +suite: tablet-pod-annotations +templates: + - templates/sts-tablet.yaml + +tests: + - it: should render pod annotations on tablet + set: + tablet.podAnnotations: + prometheus.io/scrape: "true" + prometheus.io/port: "9249" + asserts: + - equal: + path: spec.template.metadata.annotations + value: + prometheus.io/scrape: "true" + prometheus.io/port: "9249" + + - it: should not apply coordinator annotations to tablet + set: + coordinator.podAnnotations: + prometheus.io/scrape: "true" + asserts: + - isNull: + path: spec.template.metadata.annotations + +--- + +suite: coordinator-pod-annotations +templates: + - templates/sts-coordinator.yaml + +tests: + - it: should render pod annotations on coordinator + set: + coordinator.podAnnotations: + prometheus.io/scrape: "true" + asserts: + - equal: + path: spec.template.metadata.annotations + value: + prometheus.io/scrape: "true" + +--- + +suite: tablet-pod-labels +templates: + - templates/sts-tablet.yaml + +tests: + - it: should render custom pod labels on tablet + set: + tablet.podLabels: + team: data-platform + env: production + asserts: + - equal: + path: spec.template.metadata.labels.team + value: data-platform + - equal: + path: spec.template.metadata.labels.env + value: production + + - it: should preserve built-in labels when custom labels are added + set: + tablet.podLabels: + custom: label + asserts: + - equal: + path: spec.template.metadata.labels["app.kubernetes.io/component"] + value: tablet + - equal: + path: spec.template.metadata.labels.custom + value: label + +--- + +suite: coordinator-pod-labels +templates: + - templates/sts-coordinator.yaml + +tests: + - it: should render custom pod labels on coordinator + set: + coordinator.podLabels: + team: data-platform + asserts: + - equal: + path: spec.template.metadata.labels.team + value: data-platform + +--- + +suite: pdb-defaults +templates: + - templates/pdb-tablet.yaml + - templates/pdb-coordinator.yaml + +tests: + - it: should not render tablet PDB by default + asserts: + - hasDocuments: + count: 0 + template: templates/pdb-tablet.yaml + + - it: should not render coordinator PDB by default + asserts: + - hasDocuments: + count: 0 + template: templates/pdb-coordinator.yaml + +--- + +suite: tablet-pdb +templates: + - templates/pdb-tablet.yaml + +tests: + - it: should render tablet PDB with minAvailable + set: + tablet.podDisruptionBudget: + enabled: true + minAvailable: 2 + asserts: + - hasDocuments: + count: 1 + - equal: + path: kind + value: PodDisruptionBudget + - equal: + path: metadata.name + value: tablet-server + - equal: + path: spec.minAvailable + value: 2 + - equal: + path: spec.selector.matchLabels["app.kubernetes.io/component"] + value: tablet + + - it: should render tablet PDB with maxUnavailable + set: + tablet.podDisruptionBudget: + enabled: true + maxUnavailable: 1 + asserts: + - equal: + path: spec.maxUnavailable + value: 1 + - isNull: + path: spec.minAvailable + +--- + +suite: coordinator-pdb +templates: + - templates/pdb-coordinator.yaml + +tests: + - it: should render coordinator PDB with minAvailable + set: + coordinator.podDisruptionBudget: + enabled: true + minAvailable: 1 + asserts: + - hasDocuments: + count: 1 + - equal: + path: kind + value: PodDisruptionBudget + - equal: + path: metadata.name + value: coordinator-server + - equal: + path: spec.minAvailable + value: 1 + - equal: + path: spec.selector.matchLabels["app.kubernetes.io/component"] + value: coordinator + +--- + +suite: pdb-validation +templates: + - templates/NOTES.txt + +tests: + - it: should fail when both minAvailable and maxUnavailable are set on tablet + set: + tablet.podDisruptionBudget: + enabled: true + minAvailable: 2 + maxUnavailable: 1 + asserts: + - failedTemplate: + errorPattern: "cannot set both minAvailable and maxUnavailable" + + - it: should fail when neither minAvailable nor maxUnavailable is set on tablet + set: + tablet.podDisruptionBudget: + enabled: true + asserts: + - failedTemplate: + errorPattern: "must set either minAvailable or maxUnavailable when enabled" + + - it: should fail when both minAvailable and maxUnavailable are set on coordinator + set: + coordinator.podDisruptionBudget: + enabled: true + minAvailable: 1 + maxUnavailable: 1 + asserts: + - failedTemplate: + errorPattern: "cannot set both minAvailable and maxUnavailable" + + - it: should fail when neither minAvailable nor maxUnavailable is set on coordinator + set: + coordinator.podDisruptionBudget: + enabled: true + asserts: + - failedTemplate: + errorPattern: "must set either minAvailable or maxUnavailable when enabled" + +--- + +suite: pdb-zero-values +templates: + - templates/pdb-tablet.yaml + +tests: + - it: should render tablet PDB with minAvailable set to zero + set: + tablet.podDisruptionBudget: + enabled: true + minAvailable: 0 + asserts: + - equal: + path: spec.minAvailable + value: 0 + + - it: should render tablet PDB with maxUnavailable set to zero + set: + tablet.podDisruptionBudget: + enabled: true + maxUnavailable: 0 + asserts: + - equal: + path: spec.maxUnavailable + value: 0 diff --git a/helm/values.yaml b/helm/values.yaml index bf9f3022e2..082b22b262 100644 --- a/helm/values.yaml +++ b/helm/values.yaml @@ -46,6 +46,12 @@ tablet: extraVolumes: [] extraVolumeMounts: [] initContainers: [] + podAnnotations: {} + podLabels: {} + podDisruptionBudget: + enabled: false + # minAvailable: 1 + # maxUnavailable: 1 # Pod scheduling configuration affinity: {} # Example: spread tablet server pods across availability zones and nodes. @@ -81,6 +87,12 @@ coordinator: extraVolumes: [] extraVolumeMounts: [] initContainers: [] + podAnnotations: {} + podLabels: {} + podDisruptionBudget: + enabled: false + # minAvailable: 1 + # maxUnavailable: 1 affinity: {} nodeSelector: {} tolerations: [] diff --git a/website/docs/install-deploy/deploying-with-helm.md b/website/docs/install-deploy/deploying-with-helm.md index 270c366df9..f38a562990 100644 --- a/website/docs/install-deploy/deploying-with-helm.md +++ b/website/docs/install-deploy/deploying-with-helm.md @@ -284,9 +284,19 @@ It is recommended to set these explicitly in production. | `coordinator.extraVolumes` | Extra volumes to add to the CoordinatorServer pod spec | `[]` | | `coordinator.extraVolumeMounts` | Extra volume mounts to add to the coordinator container | `[]` | | `coordinator.initContainers` | Init containers to run before the coordinator container starts | `[]` | +| `coordinator.podAnnotations` | Annotations to add to CoordinatorServer pods | `{}` | +| `coordinator.podLabels` | Additional labels to add to CoordinatorServer pods | `{}` | +| `coordinator.podDisruptionBudget.enabled` | Enable PodDisruptionBudget for CoordinatorServer | `false` | +| `coordinator.podDisruptionBudget.minAvailable` | Minimum available coordinator pods during disruption | Not set | +| `coordinator.podDisruptionBudget.maxUnavailable` | Maximum unavailable coordinator pods during disruption | Not set | | `tablet.extraVolumes` | Extra volumes to add to TabletServer pod specs | `[]` | | `tablet.extraVolumeMounts` | Extra volume mounts to add to the tablet container | `[]` | | `tablet.initContainers` | Init containers to run before the tablet container starts | `[]` | +| `tablet.podAnnotations` | Annotations to add to TabletServer pods | `{}` | +| `tablet.podLabels` | Additional labels to add to TabletServer pods | `{}` | +| `tablet.podDisruptionBudget.enabled` | Enable PodDisruptionBudget for TabletServer | `false` | +| `tablet.podDisruptionBudget.minAvailable` | Minimum available tablet server pods during disruption | Not set | +| `tablet.podDisruptionBudget.maxUnavailable` | Maximum unavailable tablet server pods during disruption | Not set | ## Advanced Configuration