feat: Add production-grade Helm chart for Kubernetes deployment - #4375
feat: Add production-grade Helm chart for Kubernetes deployment#4375ioaiaaii wants to merge 20 commits into
Conversation
We are introducing a dynamic deployment and cm creation, based on pollers from harvest configuration, to manage deployments dynamically. When multiple NetApp systems need to be monitored, the deployment needs to scale. For each poller, we will have an immutable set of deployments, along with their extra custom configmaps, during extensions set. Along with deployment of a poller, the helper _podmonitor.tpl creates Prometheus podMonitor resources. This is a paradigm inspired by Grafana and OTel charts. Notes: https://github.com/open-telemetry/opentelemetry-helm-charts/blob/main/charts/opentelemetry-collector/templates/_pod.tpl
Keeping conf DRY, we use the same conf for pollers management based on the official harvest conf. Notes: https://netapp.github.io/harvest/latest/configure-harvest-basic/
While tested, in K8S ecosystem the pollers Deployment with podMonitor model works really well, without extra hops in the flow. We are comming the templetes for chart to be complete. This mode enables new deployment, spawning harvest in admin mode, exposes it with a headless svc, and creates a prometheus additional scrape config using http_sd spec. Pollers connect with admin pod, and got service discovered.
Functions derived and written based on multiple open-source charts examples.
- provide helm-docs comments - init defaults
- case for poller per deployment - case for admin for sd
- using helm-docs templating for values auto doc generation - documenting deployment methods - welcome note after deployment
- trigger up PR, changes in the relevant path - use well known gh actions
| persist-credentials: false | ||
|
|
||
| - name: Set up Helm | ||
| uses: azure/setup-helm@v4 |
| python-version: '3.x' | ||
|
|
||
| - name: Set up chart-testing | ||
| uses: helm/chart-testing-action@v2 |
| ct lint \ | ||
| --chart-dirs deploy/helm \ | ||
| --validate-maintainers=false \ | ||
| --target-branch ${{ github.event.repository.default_branch }} |
There was a problem hiding this comment.
Pull request overview
This PR introduces a Kubernetes-native Helm chart for deploying Harvest in production, including per-poller Deployments and Prometheus Operator integration (PodMonitor/ScrapeConfig), plus CI linting and documentation tooling.
Changes:
- Adds a new Helm chart under
deploy/helm/harvestwith poller/admin components, config rendering, and generated documentation. - Adds Helm linting/documentation targets (
make helm-lint,make helm-docs) and a GitHub Actions workflow for chart linting. - Extends commitlint allowed scopes to include
deploy.
Reviewed changes
Copilot reviewed 22 out of 22 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
| Makefile | Adds Helm helper targets for linting and docs generation. |
| commitlint.config.mjs | Adds deploy to allowed commit scopes. |
| .github/workflows/helm-lint.yml | Adds CI job to lint Helm charts in deploy/helm. |
| deploy/helm/harvest/Chart.yaml | Introduces the Helm chart metadata (name/version/appVersion). |
| deploy/helm/harvest/values.yaml | Defines chart values for pollers, monitoring, and hardening defaults. |
| deploy/helm/harvest/.helmignore | Excludes template inputs/fixtures from chart packaging. |
| deploy/helm/harvest/README.md.gotmpl | Adds helm-docs template source for chart documentation. |
| deploy/helm/harvest/README.md | Adds generated user documentation for chart usage and values. |
| deploy/helm/harvest/templates/_helpers.tpl | Adds helper templates for names, labels, image, and checksum. |
| deploy/helm/harvest/templates/configmap.yaml | Renders harvest.yml ConfigMap with chart-only key stripping and admin injection. |
| deploy/helm/harvest/templates/NOTES.txt | Adds post-install guidance and poller listing. |
| deploy/helm/harvest/templates/pollers/deployment.yaml | Iterates pollers to render one Deployment per poller. |
| deploy/helm/harvest/templates/pollers/podmonitor.yaml | Iterates pollers to render one PodMonitor per poller. |
| deploy/helm/harvest/templates/pollers/collectors-extension-cm.yaml | Generates per-poller/per-collector extension ConfigMaps. |
| deploy/helm/harvest/templates/pollers/_deployment.tpl | Core poller Deployment template including probes, securityContext, mounts. |
| deploy/helm/harvest/templates/pollers/_podmonitor.tpl | Core poller PodMonitor template with per-poller overrides. |
| deploy/helm/harvest/templates/admin/deployment.yaml | Adds optional admin Deployment for HTTP SD mode. |
| deploy/helm/harvest/templates/admin/service.yaml | Adds headless Service for the admin SD endpoint. |
| deploy/helm/harvest/templates/admin/scrapeconfig.yaml | Adds ScrapeConfig for Prometheus Operator HTTP SD scraping. |
| deploy/helm/harvest/ci/pollers.yaml | CI fixture values for multi-poller + extensions + PodMonitor overrides. |
| deploy/helm/harvest/ci/admin.yaml | CI fixture values for admin SD mode. |
| deploy/helm/harvest/ci/existing-configmap.yaml | CI fixture values for externally managed harvest.yml ConfigMap. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| {{- if .Values.monitoring.scrape.podMonitor.enabled | default true -}} | ||
| {{- range $name, $poller := .Values.harvestConfig.Pollers }} | ||
| {{- $ctx := dict "root" $ "pollerName" $name "poller" $poller -}} | ||
| {{ include "harvest.poller.podmonitor" $ctx }} | ||
| --- | ||
| {{- end }} | ||
| {{- end }} |
There was a problem hiding this comment.
Nice catch!
Fixed at cdd2e1e
helm template . -s templates/pollers/podmonitor.yaml -n harvest -f ci/pollers.yaml --set monitoring.scrape.podMonitor.enabled=true | grep Source:
# Source: harvest/templates/pollers/podmonitor.yaml
# Source: harvest/templates/pollers/podmonitor.yaml
helm template . -s templates/pollers/podmonitor.yaml -n harvest -f ci/pollers.yaml --set monitoring.scrape.podMonitor.enabled=false | grep Source:
Error: could not find template templates/pollers/podmonitor.yaml in chart
| {{- $pm := (get $p "podMonitor") | default dict -}} | ||
| {{- $enabled := (coalesce (get $pm "enabled") ($root.Values.monitoring.scrape.podMonitor.enabled | default true)) -}} | ||
| {{- if $enabled }} |
There was a problem hiding this comment.
Nice catch!
Fixed at cdd2e1e
helm template . -s templates/pollers/podmonitor.yaml -n harvest -f ci/pollers.yaml --set monitoring.scrape.podMonitor.enabled=true | grep Source:
# Source: harvest/templates/pollers/podmonitor.yaml
# Source: harvest/templates/pollers/podmonitor.yaml
helm template . -s templates/pollers/podmonitor.yaml -n harvest -f ci/pollers.yaml --set monitoring.scrape.podMonitor.enabled=false | grep Source:
Error: could not find template templates/pollers/podmonitor.yaml in chart
| spec: | ||
| httpSDConfigs: | ||
| - url: "http://{{ include "harvest.fullname" $ }}-admin-sd.{{ .Release.Namespace }}.svc.cluster.local:{{ .Values.admin.service.ports.httpsd }}/api/v1/sd" | ||
|
|
| name: {{ include "harvest.fullname" $root }}-poller-{{ $name }} | ||
| labels: | ||
| {{- include "harvest.labels" $root | nindent 4 }} | ||
| app.kubernetes.io/component: poller-{{ $name }} | ||
| spec: |
There was a problem hiding this comment.
Handled with a fail-fast check derived from https://medium.com/@allprog/helms-default-name-truncation-is-bad-120f9033edcc.
New helper abstract function added in b3c33bf to validate the DNS label standard as defined in RFC 1123 and can be reused elsewhere if needed.
poller deployment updated in 2f73351 and verified with:
helm template . -s templates/pollers/podmonitor.yaml -n harvest -f ci/pollers.yaml --set "harvestConfig.Pollers.Non_RFC1123.addr=1.1.1.1" --set "harvestConfig.Pollers.Non_RFC1123.prom_port=13000"
Error: execution error at (harvest/templates/pollers/deployment.yaml:2:4): invalid Kubernetes name "poller-Non_RFC1123": use 63 or fewer chars of lowercase a-z, 0-9 and '-', starting and ending alphanumeric
helm template . -s templates/pollers/deployment.yaml -n harvest -f ci/pollers.yaml --set "harvestConfig.Pollers.rfc-1123.addr=1.1.1.1" --set "harvestConfig.Pollers.rfc-1123.prom_port=13000" | grep Source:
# Source: harvest/templates/pollers/deployment.yaml
# Source: harvest/templates/pollers/deployment.yaml
# Source: harvest/templates/pollers/deployment.yaml
| Verify metrics are being scraped (from inside the cluster): | ||
| {{- range $name, $p := .Values.harvestConfig.Pollers }} | ||
| kubectl exec -n {{ $.Release.Namespace }} deploy/{{ include "harvest.fullname" $ }}-poller-{{ $name }} -- wget -qO- http://localhost:{{ $p.prom_port }}/metrics | head -5 | ||
| {{- end }} | ||
| {{- end }} |
There was a problem hiding this comment.
Yep, fixup at dd1ce00, looks cleaner now:
NOTES:
NetApp Harvest has been deployed!
Check poller pods:
kubectl get pods -n harvest -l app.kubernetes.io/instance=rel
Pollers configured: 2
- cluster-a (port 13000)
- cluster-b (port 13001)
Verify a poller is exporting metrics. Port-forward it, then curl locally in another terminal:
kubectl port-forward -n harvest deploy/rel-harvest-poller-cluster-a 13000:13000
curl -s http://localhost:13000/metrics | head
kubectl port-forward -n harvest deploy/rel-harvest-poller-cluster-b 13001:13001
curl -s http://localhost:13001/metrics | head
PodMonitor resources created. Make sure the Prometheus Operator selects them via podMonitorSelector or podMonitorNamespaceSelector.
Visit https://netapp.github.io/harvest/latest for instructions on how to create and configure Harvest.
| {{- if and .Values.monitoring.scrape.adminSD.enabled (not (hasKey $cfg "Admin")) -}} | ||
| {{- $_ := set $cfg "Admin" (dict "httpsd" (dict "listen" (printf ":%v" .Values.admin.containerPorts.httpsd))) -}} | ||
| {{- end -}} |
There was a problem hiding this comment.
I think it would be better to have it as is, abstract, without adding more implementation-specific logic.
|
The |
| RestPerf -> /opt/harvest/conf/restperf/custom.yaml | ||
| */ -}} | ||
| {{- range $collectorType, $ext := $exts }} | ||
| {{- if (get $ext "objects") }} |
There was a problem hiding this comment.
custom.yaml may still be valid without just objects. For example, we may want to overwrite the default schedule or add jitter, etc. Valid for line 152 as well.
There was a problem hiding this comment.
Thanks for reviewing the PR @rahulguptajss !
I'll take a look and get back to you on this one, along with the other suggestions from Copilot.
There was a problem hiding this comment.
Nice catch!
Added this case in ci/pollers.yaml with 3b2d65a
and mitigated in cm + deployment with be0e512.
Tested with:
helm template . -n harvest -f ci/pollers.yaml
# Source: harvest/templates/pollers/collectors-extension-cm.yaml
..
metadata:
name: release-name-harvest-poller-cluster-a-rest-custom
...
custom.yaml: |
jitter: 1m
schedule:
- data: 80s
---
# Source: harvest/templates/pollers/collectors-extension-cm.yaml
..
metadata:
name: release-name-harvest-poller-cluster-a-restperf-custom
....
custom.yaml: |
jitter: 1m
objects:
OntapS3SVM: ontap_s3_svm.yaml
Qtree: qtree.yaml
schedule:
- data: 180s
---
# Source: harvest/templates/pollers/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: release-name-harvest-poller-cluster-a
.....
volumeMounts:
- name: harvest-config
mountPath: /opt/harvest/harvest.yml
subPath: harvest.yml
- name: custom-cluster-a-rest
mountPath: /opt/harvest/conf/rest/custom.yaml
subPath: custom.yaml
- name: custom-cluster-a-restperf
mountPath: /opt/harvest/conf/restperf/custom.yaml
subPath: custom.yaml
volumes:
- name: harvest-config
configMap:
name: release-name-harvest-config
- name: custom-cluster-a-rest
configMap:
name: release-name-harvest-poller-cluster-a-rest-custom
- name: custom-cluster-a-restperf
configMap:
name: release-name-harvest-poller-cluster-a-restperf-custom
Summary
This PR contributes the Kubernetes-native Helm chart proposed and discussed in #4167. We have been running it in production at GRNET, monitoring multiple ONTAP clusters through the Prometheus Operator, and would like to upstream it.
Suggested Changes
Declarative multi-poller deployment
Each entry under
harvestConfig.Pollersgenerates:Deployment(one poller per container)PodMonitorConfigMapsTwo scrape modes
directly through a
PodMonitor.adminDeployment servesHarvest's
/api/v1/sdendpoint, fronted by a headlessServiceand aScrapeConfig, so newpollers are discovered automatically.
Credentials
The chart does not create or manage credential Secrets. A poller's
passworduses a${VAR}placeholder, and the real value comes from a Kubernetes Secret through that poller's
extraEnvVars. Harvest expands${VAR}at load time, soharvest.ymlholds only placeholdersand the credential reaches a single poller's pod. This keeps secrets out of Helm values and
works with Vault, the External Secrets Operator, or sealed-secrets. A complete external
harvest.ymlcan also be supplied throughconfig.existingConfigMap.Production Hardening
priorityClassNamereadOnlyRootFilesystem,allowPrivilegeEscalation: false, capability drop (ALL, keepingonly
NET_RAWfor the ICMP poller-status metric),seccompProfile: RuntimeDefaultautomountServiceAccountToken: false(Harvest does not call the Kubernetes API)Chart structure
CI and tooling
.github/workflows/helm-lint.ymlrunshelm lintagainst theci/value sets (default,multi-poller, admin SD, existing-ConfigMap)
make helm-lintandmake helm-docsdeployscope added to commitlintValidation
helm lintandhelm templateclean across allci/fixtureskubeconform${VAR}placeholders, per-poller Secretenv isolation confirmed, and collector
custom.yamlmounted at the path Harvest readsFollow-up work
To keep this PR reviewable, the chart core focuses on poller deployments, the optional admin SD component, config rendering, credentials, security hardening, and CI.
The following are intentionally left for separate PRs:
helm unittestsuite wired into CIcollectorsExtensions(today it enables objects theimage already ships; adding a brand-new object template is a follow-up)