Skip to content

feat: Add production-grade Helm chart for Kubernetes deployment - #4375

Open
ioaiaaii wants to merge 20 commits into
NetApp:mainfrom
ioaiaaii:feat/4167-helm-chart
Open

feat: Add production-grade Helm chart for Kubernetes deployment#4375
ioaiaaii wants to merge 20 commits into
NetApp:mainfrom
ioaiaaii:feat/4167-helm-chart

Conversation

@ioaiaaii

@ioaiaaii ioaiaaii commented Aug 1, 2026

Copy link
Copy Markdown

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

Image

Each entry under harvestConfig.Pollers generates:

  • a Deployment (one poller per container)
  • a matching PodMonitor
  • optional collector-extension ConfigMaps

Two scrape modes

  • Per-poller Prometheus port (default): each poller exposes its own port and is scraped
    directly through a PodMonitor.
  • HTTP Service Discovery (optional, currently experimental): an admin Deployment serves
    Harvest's /api/v1/sd endpoint, fronted by a headless Service and a ScrapeConfig, so new
    pollers are discovered automatically.

Credentials

The chart does not create or manage credential Secrets. A poller's password uses a ${VAR}
placeholder, and the real value comes from a Kubernetes Secret through that poller's
extraEnvVars. Harvest expands ${VAR} at load time, so harvest.yml holds only placeholders
and 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.yml can also be supplied through config.existingConfigMap.

Production Hardening

  • resource requests/limits, startup/readiness/liveness probes
  • affinity, topology spread, tolerations, priorityClassName
  • readOnlyRootFilesystem, allowPrivilegeEscalation: false, capability drop (ALL, keeping
    only NET_RAW for the ICMP poller-status metric), seccompProfile: RuntimeDefault
  • automountServiceAccountToken: false (Harvest does not call the Kubernetes API)

Chart structure

deploy/helm/harvest/
├── Chart.yaml
├── values.yaml
├── README.md              # generated from README.md.gotmpl via helm-docs
├── ci/                    # values fixtures for helm lint
└── templates/
    ├── pollers/           # Deployment + PodMonitor per poller, collector extensions
    ├── admin/             # admin SD Deployment, Service, ScrapeConfig
    ├── configmap.yaml     # harvest.yml
    ├── _helpers.tpl
    └── NOTES.txt

CI and tooling

  • .github/workflows/helm-lint.yml runs helm lint against the ci/ value sets (default,
    multi-poller, admin SD, existing-ConfigMap)
  • make helm-lint and make helm-docs
  • deploy scope added to commitlint

Validation

  • helm lint and helm template clean across all ci/ fixtures
  • rendered manifests validated against Kubernetes and prometheus-operator CRD schemas with kubeconform
  • deployed to a local kind cluster: config mounted with ${VAR} placeholders, per-poller Secret
    env isolation confirmed, and collector custom.yaml mounted at the path Harvest reads
  • output compared against our production chart for parity

Follow-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:

ioaiaaii added 12 commits August 1, 2026 12:05
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
Comment thread .github/workflows/helm-lint.yml Outdated
persist-credentials: false

- name: Set up Helm
uses: azure/setup-helm@v4

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mitigated in 05a32cf

Comment thread .github/workflows/helm-lint.yml Outdated
python-version: '3.x'

- name: Set up chart-testing
uses: helm/chart-testing-action@v2

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mitigated in 05a32cf

Comment thread .github/workflows/helm-lint.yml Outdated
ct lint \
--chart-dirs deploy/helm \
--validate-maintainers=false \
--target-branch ${{ github.event.repository.default_branch }}

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mitigated in 05a32cf

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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/harvest with 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.

Comment on lines +1 to +7
{{- 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 }}

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Comment on lines +33 to +35
{{- $pm := (get $p "podMonitor") | default dict -}}
{{- $enabled := (coalesce (get $pm "enabled") ($root.Values.monitoring.scrape.podMonitor.enabled | default true)) -}}
{{- if $enabled }}

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Comment on lines +13 to +16
spec:
httpSDConfigs:
- url: "http://{{ include "harvest.fullname" $ }}-admin-sd.{{ .Release.Namespace }}.svc.cluster.local:{{ .Values.admin.service.ports.httpsd }}/api/v1/sd"

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed in 7789472

Comment on lines +29 to +33
name: {{ include "harvest.fullname" $root }}-poller-{{ $name }}
labels:
{{- include "harvest.labels" $root | nindent 4 }}
app.kubernetes.io/component: poller-{{ $name }}
spec:

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Comment thread deploy/helm/harvest/templates/NOTES.txt Outdated
Comment on lines +21 to +25
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 }}

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment on lines +8 to +10
{{- 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 -}}

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it would be better to have it as is, abstract, without adding more implementation-specific logic.

@ioaiaaii ioaiaaii changed the title feat(deploy): add a production-grade Helm chart for Harvest on Kubernetes feat: Add production-grade Helm chart for Kubernetes deployment Aug 4, 2026
@ioaiaaii

ioaiaaii commented Aug 4, 2026

Copy link
Copy Markdown
Author

The deploy scope was removed from the PR title to pass the linter.
Will take a look at the review suggestions.

RestPerf -> /opt/harvest/conf/restperf/custom.yaml
*/ -}}
{{- range $collectorType, $ext := $exts }}
{{- if (get $ext "objects") }}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

@ioaiaaii
ioaiaaii requested a review from rahulguptajss August 8, 2026 09:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants