Skip to content

feat(qdrant): add Qdrant vector database chart - #310

Merged
arunesh-j merged 10 commits into
mainfrom
feat/qdrant
Aug 25, 2026
Merged

feat(qdrant): add Qdrant vector database chart#310
arunesh-j merged 10 commits into
mainfrom
feat/qdrant

Conversation

@NitinKumar004

Copy link
Copy Markdown
Contributor

Description

Adds a new datasource Helm chart for Qdrant — a high-performance vector database for AI workloads (semantic search, recommendations, RAG). This fills a genuine gap in the catalog: we ship ChromaDB (prototype-grade) but had no production vector store, and Qdrant is the on-trend choice teams actually ship.

The chart deploys a single-node Qdrant with persistence, API-key authentication, and built-in Prometheus monitoring — following the richer ClickHouse-style pattern already established in this repo.

Architecture

flowchart TB
    subgraph ns["Namespace"]
        subgraph sts["StatefulSet: <release>-qdrant (replicas: 1, non-root uid 1000)"]
            pod["qdrant/qdrant:v1.19.0<br/>REST :6333 · gRPC :6334<br/>probes: /livez /readyz /healthz"]
        end
        pvc[("PVC 10Gi<br/>/qdrant/storage<br/>(data + snapshots + init file)")]
        sec["Secret<br/>&lt;release&gt;-qdrant-apikey-secret<br/>api-key (generated, preserved on upgrade)"]
        cm["ConfigMap<br/>&lt;release&gt;-qdrant-configmap<br/>QDRANT_HOST / HTTP_PORT / GRPC_PORT"]
        ccm["ConfigMap (optional)<br/>custom-config → /qdrant/config/local.yaml"]
        svc["Service (ClusterIP)<br/>6333 http · 6334 grpc"]
        hsvc["Service (headless)<br/>stable StatefulSet identity"]
        sm["ServiceMonitor<br/>scrapes /metrics with Bearer key"]
        pr["PrometheusRule<br/>QdrantDown alert"]
    end

    pod --- pvc
    sec -->|QDRANT__SERVICE__API_KEY env| pod
    ccm -.->|mounted| pod
    svc --> pod
    hsvc --> pod
    sm -->|Authorization: Bearer| svc
    pr -.->|up == 0| sm

    app["Application pod"] -->|host + ports from ConfigMap<br/>api-key header| svc
    prom["Prometheus"] --> sm
Loading

Consuming applications read QDRANT_HOST / QDRANT_HTTP_PORT / QDRANT_GRPC_PORT from the connection ConfigMap and pass the API key (from the Secret) in the api-key header. Qdrant has no databases/users — apps create collections at runtime — so there is no SQL-style init job.

What's included

charts/qdrant/
├── Chart.yaml            # v2, version 0.0.1, annotations.type=datasource, maintainers: ZopDev
├── values.yaml
├── values.schema.json    # UI categories: compute / runtime / advanced (mutable / editDisabled)
├── README.md
└── templates/
    ├── _helpers.tpl
    ├── statefulset.yaml          # non-root, PVC at /qdrant/storage, API key env, probes
    ├── service.yaml              # ClusterIP 6333 (http/metrics) + 6334 (grpc)
    ├── service-headless.yaml
    ├── secret.yaml               # generated API key, preserved across upgrades
    ├── configmap.yaml            # connection info for apps
    ├── custom-config-configmap.yaml   # optional raw Qdrant YAML + Stakater reloader
    ├── serviceMonitor.yaml       # authenticated /metrics scrape (no exporter sidecar)
    └── alerts.yaml               # PrometheusRule: QdrantDown

Also packaged as docs/qdrant-v0.0.1.tgz and added to docs/index.yaml (merged in by hand to avoid churning existing entries' timestamps).

Configuration

Parameter Description Default
version Qdrant image tag (qdrant/qdrant:<version>) v1.19.0
diskSize PVC size backing /qdrant/storage 10Gi
resources.requests.cpu / .memory Requests 250m / 512Mi
resources.limits.cpu / .memory Limits 1000m / 2Gi
customConfig Extra Qdrant config (YAML) → /qdrant/config/local.yaml ""

Testing — end-to-end on a real cluster

Deployed on a local minikube cluster (with the Prometheus-Operator CRDs installed to mirror production). This surfaced two real bugs that helm lint alone would never catch, both now fixed:

  1. CrashLoopBackOff — snapshots dir not writable. As non-root uid 1000, only the /qdrant/storage PVC is writable; the image's /qdrant/snapshots and working dir are root-owned, so Qdrant panicked on startup. Fixed by pinning QDRANT__STORAGE__SNAPSHOTS_PATH and QDRANT_INIT_FILE_PATH onto the PVC.
  2. ServiceMonitor would 401. On v1.19.0, /metrics requires the API key when auth is enabled (only the health endpoints stay open). Fixed by scraping with the key as a Bearer token from the Secret.

All 12 cases pass after the fixes:

Case Result
Install → all resources created, PVC bound
Pod 1/1 Running, clean startup
Health /livez /readyz /healthz open without key
/metrics returns Prometheus format (authenticated)
Data API without key → 401 rejected
Data API with api-key header → 200
Auth via Authorization: Bearer → 200
Data-plane: create collection, upsert, vector search
gRPC :6334 reachable (HTTP/2)
API key preserved across upgrades
Data persists across pod restart (PVC)
customConfig honored (telemetry log flips to disabled)
Default release has no custom-config artifacts
Clean helm uninstall

Product integration

No kube-management-api or app-ui changes required. The maintainers[0].name: ZopDev + annotations: type: datasource metadata makes the chart classify as a datasource via the existing fallback path, and the UI form renders generically from values.schema.json.

Type of Change

  • New feature
  • Chart configuration update

Checklist

  • Chart conforms to Helm best practices
  • helm lint passes without errors
  • End-to-end tested on a live cluster (install, data-plane, auth, metrics, persistence, upgrade, uninstall)
  • Packaged and indexed in docs/
  • Documentation (chart README.md) added

Add a datasource Helm chart for a single-node Qdrant (v1.19.0):
- StatefulSet on a persistent volume at /qdrant/storage, non-root uid 1000
- REST (6333) + gRPC (6334); ClusterIP + headless services
- Generated API key stored in a Secret and preserved across upgrades
- Built-in Prometheus /metrics scraped by an authenticated ServiceMonitor,
  plus a PrometheusRule with a QdrantDown alert
- Optional customConfig mounted at /qdrant/config/local.yaml with reloader
- values.schema.json with UI categories for the zop.dev datasource form

Packaged as qdrant-v0.0.1.tgz and indexed in docs/index.yaml.

@arunesh-j arunesh-j 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.

Review: feat(qdrant): add Qdrant vector database chart

Reviewed at 3ce6741 in an isolated worktree, and installed on a local minikube.
The chart is well built — the non-root securityContext, the snapshot-path
redirect, the lookup-preserved API key and the customConfig mount all work
exactly as documented, which I confirmed on a cluster rather than by reading.

Findings below are ordered by consequence. Two are blocking, and one of them I
was able to reproduce as a hard helm install failure.


Blocking

1. alerts.yaml:4 — the PrometheusRule name collides with every other datastore chart

metadata:
  name: {{ .Release.Name }}

Qdrant is a datasource chart, so its normal use is as a dependency of an app
chart — and app charts here almost always also pull in postgres. postgres,
redis, mysql, clickhouse, solr and kafka all name their PrometheusRule
{{ .Release.Name }} too, so the release contains two objects of the same kind
and name and Helm refuses the install outright.

Reproduced with an umbrella chart depending on qdrant + postgres:

Error: INSTALLATION FAILED: 1 error occurred:
	* prometheusrules.monitoring.coreos.com "myapp" already exists

Fix — name it after the chart, as localai and holmesgpt do. The helper is
already defined in this chart and currently never used (see note 8):

metadata:
  name: {{ include "qdrant.fullname" . }}

2. Chart.yaml:6 — the icon hotlinks qdrant.tech, and the URL is dead

icon: "https://qdrant.tech/images/logo_with_text.svg"

curl -sI on it returns HTTP 404, so the chart renders logo-less on
helm.zop.dev today. Separately, this is the only chart of the 32 in the repo
pointing at a third-party host — every other icon is served from
storage.googleapis.com/zopdev-* or zop.dev/logo.png. An image the repo does
not control can 404, be replaced, or start serving something else, on a zop.dev
product surface.

This does not fail CI (icon is recommended is INFO), but it is a reject-level
rule for this repo. Please upload the logo to
storage.googleapis.com/zopdev-blog-resources/ — as clickhouse, litellm,
localai, holmesgpt and n8n do — and point icon: there. A reviewer cannot
upload to the bucket, so this one needs you.

3. README.md:5-9 — the Prometheus Operator CRDs are an undeclared prerequisite

alerts.yaml and serviceMonitor.yaml are ungated, so every install emits a
ServiceMonitor and a PrometheusRule. On a cluster without
kube-prometheus-stack, helm install fails with no matches for kind "ServiceMonitor" in version "monitoring.coreos.com/v1". Prerequisites currently
promise only Kubernetes 1.19+ and Helm 3+.

Ungating matches the datastore family (postgres, mysql, clickhouse, kafka
are all unconditional), so I am not asking you to add a metrics.enabled flag —
just to state the CRD requirement in Prerequisites so nobody walks into a failed
install.

Not verified on a CRD-less cluster — the minikube I tested on already had the
CRDs installed. This one is reasoned from the templates.


Should fix

4. /metrics is NOT open — README.md:59 and statefulset.yaml:46-48 say it is

Measured against the running container, through the Service:

Request Status
/metrics no key 401
/metrics with api-key header 200
/livez, /readyz, /healthz no key 200

So this comment on the container env is wrong:

# The health and /metrics endpoints stay open, so probes and the
# ServiceMonitor keep working without the key.

and so is README.md:59 ("The health endpoints ... and /metrics remain open"),
which also contradicts README.md:108, which gets it right.

The ServiceMonitor itself is correct — it sends the key as a Bearer token,
and that is the only reason scraping works. The risk is purely that the comment
invites a future reader to delete the authorization: block as redundant, which
would silently break metrics collection. Please correct both spots.

5. serviceMonitor.yaml:9-11 — the selector matches both Services, so everything is scraped twice

service.yaml:7 and service-headless.yaml:7 both carry app: <release>-qdrant,
and the ServiceMonitor selects on exactly that label. Verified — both back the
same pod:

r310-qdrant             [('10.244.0.76', True)]
r310-qdrant-headless    [('10.244.0.76', True)]

Two consequences:

  • Every Qdrant metric is ingested twice, doubling scrape load and series count.
  • service-headless.yaml:12 sets publishNotReadyAddresses: true, so during
    startup and every rolling upgrade Prometheus scrapes a not-ready pod and gets
    up == 0. QdrantDown (alerts.yaml:17) is for: 0m, so a critical alert
    fires on every single upgrade.

Fix: narrow the ServiceMonitor to the ClusterIP Service — e.g. give the headless
one a distinguishing label (app: <release>-qdrant-headless, keeping its pod
selector unchanged), or add a discriminating label to the scrape target. Raising
for: to 2m on QdrantDown is worth doing regardless.

6. alerts.yaml:27-28QdrantHighRestResponseErrors can never fire

The expression uses rest_responses_total, which Qdrant does not export. I
dumped every metric name from a live v1.19.0 /metrics, including at
?detail_level=10, and there is no rest_ or grpc_ metric at all (0 matches).
Qdrant tracks REST responses only under /telemetry (requests.rest.responses),
which Prometheus never sees.

The alert is inert — it reads as protection and provides none. Either drop it, or
build one on a metric that exists. From the live output, app_status_recovery_mode
is the useful one:

- alert: QdrantInRecoveryMode
  expr: app_status_recovery_mode{namespace="...", service="..."} == 1

Other real metrics available: collection_dead_replicas, collection_points,
memory_resident_bytes, process_open_fds, snapshot_creation_running.

7. values.schema.json — no description on any leaf

version, diskSize, customConfig and the four resources.* leaves all lack
description. The schema drives the zop.dev configuration form, so these render
as unlabelled inputs. charts/litellm/values.schema.json is the reference shape:

"version": {
  "category": "runtime",
  "type": "string",
  "description": "Qdrant image tag (qdrant/qdrant:<version>)",
  "default": "v1.19.0",
  "mutable": true
}

The good text already exists as comments in values.yaml — it just needs to be
in the schema too.


Notes

  1. _helpers.tpl:4 defines qdrant.fullname and nothing uses it — every
    template hardcodes {{ .Release.Name }}-qdrant. Adopting it fixes finding 1.
    The helper set is also missing .name, .chart and .labels, which the repo
    convention includes.
  2. Chart.yaml:2appVersion: "1.0" while the shipped image is v1.19.0.
    Mixed precedent (clickhouse and localai also say "1.0"), but litellm
    (1.83.14) and holmesgpt (0.39.0) track the real version. "1.19.0" here.
  3. Branch is feat/qdrant; CONTRIBUTING §2 asks for feature/, fix/ or
    docs/. Cosmetic at this point, noting for the next PR.
  4. _helpers.tpl:19 mixes app.kubernetes.io/part-of into an otherwise flat
    label set. The repo convention is the flat app / chart / release set.
    Note the StatefulSet selector is immutable, so this is easier to settle now
    than later.
  5. No NOTES.txt. Mixed precedent (localai and holmesgpt ship one, the
    datastores do not), but this chart generates a credential the operator has to
    go fetch — the retrieval command and the retained-PVC warning would sit well
    there.
  6. helm.zop.dev renders from a hardcoded list in docs/src/js/config.js, so
    qdrant will not appear on the site after merge. Repo-level drift tracked in
    #308, not a condition of this PR.

Verified clean

Reproduced CI and the packaging checks — no problems:

  • helm dependency update + helm lint0 failed, no warnings.
  • docs/index.yaml: added: 1, removed: 0, changed: 0 — no other chart's entry
    touched. 181 tarball digests verified. docs/qdrant-v0.0.1.tgz unpacks to
    exactly the chart source, v prefix correct, annotations.type: datasource
    preserved in the packaged Chart.yaml. No committed subchart tarballs, no hard
    tabs.
  • Schema: no additionalProperties: false at root, all four category values
    legal, diskSize carrying both mutable and editDisabled matches all 30
    instances of that pair in the repo.

Cluster test — minikube, helm install into a dedicated namespace:

  • Pod reached Ready with 0 restarts.
  • Non-root securityContext genuinely works: container runs uid=1000 gid=2000
    with supplementary group 3000, and writes to the freshly provisioned PVC
    succeed. Created a collection, inserted a point, and took a snapshot — the
    QDRANT__STORAGE__SNAPSHOTS_PATH redirect to /qdrant/storage/snapshots is
    what makes that work, and the directory comes out 1000:2000.
  • helm upgrade preserved the API key byte-for-byte and the collection
    survived — the lookup branch does what it claims.
  • customConfig mounts at /qdrant/config/local.yaml and takes effect (set
    log_level: DEBUG, DEBUG lines appeared in the logs).

Nice chart overall — findings 1 and 2 are the ones that need you before merge.

@arunesh-j arunesh-j 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.

Posting the findings inline against the lines they refer to, so they are easier to work through than the summary review. Same content, nothing new — the blocking ones are the PrometheusRule name in alerts.yaml, the 404ing icon in Chart.yaml, and the undeclared CRD prerequisite in the README.

Comment thread charts/qdrant/templates/alerts.yaml Outdated
apiVersion: monitoring.coreos.com/v1
kind: PrometheusRule
metadata:
name: {{ .Release.Name }}

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.

Blocking — this name collides and makes helm install fail.

qdrant is a datasource chart, so its normal use is as a dependency of an app chart — and app charts here almost always also pull in postgres. postgres, redis, mysql, clickhouse, solr and kafka all name their PrometheusRule {{ .Release.Name }} too, so the release ends up with two objects of the same kind and name.

I reproduced it with an umbrella chart depending on qdrant + postgres:

Error: INSTALLATION FAILED: 1 error occurred:
	* prometheusrules.monitoring.coreos.com "myapp" already exists

localai and holmesgpt avoid this by naming it after the chart. The helper is already defined in this chart (_helpers.tpl:4) and currently unused:

Suggested change
name: {{ .Release.Name }}
name: {{ include "qdrant.fullname" . }}

Comment thread charts/qdrant/templates/alerts.yaml Outdated
Comment on lines +27 to +28
- alert: QdrantHighRestResponseErrors
expr: increase(rest_responses_total{namespace="{{ .Release.Namespace }}", service="{{ .Release.Name }}-qdrant", status=~"5.."}[5m]) > 0

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.

Should fix — this alert can never fire.

rest_responses_total is not a metric Qdrant exposes. I dumped every metric name from a live v1.19.0 /metrics, including at ?detail_level=10, and there is no rest_ or grpc_ metric at all (0 matches). Qdrant tracks REST responses only under /telemetry (requests.rest.responses), which Prometheus never sees.

An inert alert is worse than no alert, because it reads as coverage. Either drop it, or rebuild it on a metric that exists. From the live output, the genuinely useful one is:

- alert: QdrantInRecoveryMode
  expr: app_status_recovery_mode{namespace="{{ .Release.Namespace }}", service="{{ .Release.Name }}-qdrant"} == 1

Other real metrics available: collection_dead_replicas, collection_points, memory_resident_bytes, process_open_fds, snapshot_creation_running.

Comment thread charts/qdrant/Chart.yaml Outdated
description: Helm chart for deploying qdrant vector database datastore
name: qdrant
version: 0.0.1
icon: "https://qdrant.tech/images/logo_with_text.svg"

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.

Blocking — off-domain icon, and the URL is dead.

curl -sI on this returns HTTP 404, so the chart renders logo-less on helm.zop.dev today.

Separately, this is the only chart of the 32 in the repo pointing at a third-party host — every other icon is served from storage.googleapis.com/zopdev-* or zop.dev/logo.png. An image the repo does not control can 404, be replaced, or start serving something else, on a zop.dev product surface.

This does not fail CI (icon is recommended is INFO), but it is a reject-level rule for this repo. Please upload the logo to storage.googleapis.com/zopdev-blog-resources/ — as clickhouse, litellm, localai, holmesgpt and n8n do — and point icon: there. A reviewer cannot upload to that bucket, so this one needs you.

Comment thread charts/qdrant/Chart.yaml Outdated
@@ -0,0 +1,11 @@
apiVersion: v2
appVersion: "1.0"

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.

NoteappVersion should track the app actually shipped; values.yaml pins qdrant/qdrant:v1.19.0.

Mixed precedent (clickhouse and localai also say "1.0"), but litellm (1.83.14) and holmesgpt (0.39.0) track the real version.

Suggested change
appVersion: "1.0"
appVersion: "1.19.0"

Comment on lines +46 to +48
# Enabling the API key makes Qdrant reject unauthenticated requests.
# The health and /metrics endpoints stay open, so probes and the
# ServiceMonitor keep working without the key.

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.

Should fix — this comment is factually wrong, and the error is load-bearing.

Measured against the running container, through the Service:

Request Status
/metrics no key 401
/metrics with api-key header 200
/livez, /readyz, /healthz no key 200

/metrics does not stay open. The ServiceMonitor works only because it sends the key as a Bearer token. The risk is that this comment invites a future reader to delete that authorization: block as redundant, silently breaking metrics collection.

(The probes are fine — the three health endpoints really are unauthenticated.)

{{/*
Fully qualified app name: <release>-qdrant, capped at 63 chars for DNS.
*/}}
{{- define "qdrant.fullname" -}}

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.

Note — this helper is defined and then never used; every template hardcodes {{ .Release.Name }}-qdrant instead.

Worth wiring up, because using it in alerts.yaml:4 is exactly the fix for the install-blocking PrometheusRule collision.

The helper set is also missing .name, .chart and .labels, which the repo convention includes.

Comment thread charts/qdrant/templates/_helpers.tpl Outdated
Comment on lines +18 to +19
{{- define "qdrant.selectorLabels" -}}
app.kubernetes.io/part-of: qdrant

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.

Note — this mixes an app.kubernetes.io/* label into an otherwise flat label set. The repo convention is the flat app / chart / release set rather than the upstream Helm one.

Worth settling now rather than later: this feeds spec.selector.matchLabels on the StatefulSet, which is immutable — changing it after release means users have to delete the StatefulSet to upgrade.

Comment thread charts/qdrant/README.md
Comment on lines +7 to +9
- Kubernetes 1.19+
- Helm 3+
- [Stakater Reloader](https://github.com/stakater/Reloader) (optional, recommended) — required only for the pod to restart automatically when `customConfig` changes.

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.

Blocking — the Prometheus Operator CRDs are a hard prerequisite and are not listed.

alerts.yaml and serviceMonitor.yaml are ungated, so every install emits a ServiceMonitor and a PrometheusRule. On a cluster without kube-prometheus-stack, helm install fails with:

no matches for kind "ServiceMonitor" in version "monitoring.coreos.com/v1"

Ungating matches the datastore family (postgres, mysql, clickhouse, kafka are all unconditional), so I am not asking for a metrics.enabled flag — just state the requirement so nobody walks into a failed install:

Suggested change
- Kubernetes 1.19+
- Helm 3+
- [Stakater Reloader](https://github.com/stakater/Reloader) (optional, recommended) — required only for the pod to restart automatically when `customConfig` changes.
- Kubernetes 1.19+
- Helm 3+
- **Prometheus Operator CRDs** (`ServiceMonitor`, `PrometheusRule`) — required, not optional: the chart renders both unconditionally, so `helm install` fails on a cluster without them.
- [Stakater Reloader](https://github.com/stakater/Reloader) (optional, recommended) — required only for the pod to restart automatically when `customConfig` changes.

Caveat: reasoned from the templates, not verified — the minikube I tested on already had the CRDs installed.

Comment thread charts/qdrant/README.md Outdated

## Authentication

The chart generates a strong API key on install and stores it in a Kubernetes Secret named `<release-name>-qdrant-apikey-secret` under the key `api-key`. Qdrant is started with `QDRANT__SERVICE__API_KEY` set to this value, so all data requests must be authenticated. The health endpoints (`/livez`, `/readyz`, `/healthz`) and `/metrics` remain open, so probes and monitoring work without the key.

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.

Should fix — /metrics is not open, and this contradicts line 108.

Verified against the running container: /metrics returns 401 without the key and 200 with it. Only /livez, /readyz and /healthz are unauthenticated.

Line 108 of this same README gets it right ("When the API key is enabled, /metrics requires authentication"). Please drop /metrics from this sentence so the two agree — the shipped ServiceMonitor depends on the line-108 behaviour being the true one.

Comment thread charts/qdrant/README.md Outdated
helm uninstall [RELEASE_NAME]
```

The PersistentVolumeClaim created by the StatefulSet is retained by default; delete it manually if you want to reclaim the storage.

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.

Note — the retained-PVC warning is good. Two things would round the docs out:

  1. The chart ships no templates/NOTES.txt. Mixed precedent (localai and holmesgpt ship one, the datastores do not), but this chart generates a credential the operator has to go and fetch — the retrieval command and this PVC warning would sit well there, printed at install time.
  2. Worth stating explicitly that helm uninstall deletes the API-key Secret while keeping the PVC, so a reinstall against the retained volume comes up with a new key.

…abels, docs

- alerts: name PrometheusRule after the chart (qdrant.fullname) to avoid a
  name collision with sibling datastore charts in an umbrella release; raise
  QdrantDown to for: 2m; replace the inert QdrantHighRestResponseErrors (metric
  Qdrant does not export) with QdrantInRecoveryMode on app_status_recovery_mode
- serviceMonitor/headless: give the headless Service a distinct app label so
  only the ClusterIP Service is scraped (no double scrape, no upgrade-time
  QdrantDown flap); flatten selector labels to the immutable-safe app-only set
- statefulset/README: correct the claim that /metrics is unauthenticated — it
  requires the key; only the health endpoints are open
- README: document the Prometheus Operator CRDs as a hard prerequisite; clarify
  uninstall keeps the PVC but drops the API-key Secret
- schema: add descriptions to every leaf for the datasource form
- add NOTES.txt with API-key retrieval and connection details
- Chart.yaml: appVersion 1.19.0; icon to on-domain zop.dev/logo.png (was a
  404ing third-party URL)
- repackage qdrant-v0.0.1.tgz and refresh its index digest
@NitinKumar004

Copy link
Copy Markdown
Contributor Author

Thanks for the thorough, cluster-verified review — this was exactly the kind of feedback that catches things helm lint can't. All findings addressed in 6525d29, re-tested on a fresh install.

Blocking

  1. PrometheusRule name collision — now {{ include "qdrant.fullname" . }}; renders as <release>-qdrant. Confirmed no collision.
  2. Icon — dropped the 404ing qdrant.tech URL for the on-domain https://zop.dev/logo.png (HTTP 200, same as scylladb/zookeeper/cron-job/service/uptime-monitoring). Happy to swap to a branded logo uploaded to zopdev-blog-resources/ as a follow-up.
  3. CRD prerequisite — README Prerequisites now states the Prometheus Operator CRDs are required (install fails without them).

Should fix

  1. /metrics is not open — corrected both the statefulset.yaml env comment and README.md; only /livez /readyz /healthz are unauthenticated, and the ServiceMonitor's Bearer auth is now called out as load-bearing so nobody deletes it.
  2. Double scrape / upgrade flap — the headless Service now carries a distinct app: <release>-qdrant-headless label (pod selector unchanged), so the ServiceMonitor matches only the ClusterIP Service. Also raised QdrantDown to for: 2m.
  3. Inert alert — replaced QdrantHighRestResponseErrors with QdrantInRecoveryMode on app_status_recovery_mode == 1, which I confirmed is exported by a live v1.19.0 /metrics.
  4. Schema descriptions — added to every leaf (and the root), matching the litellm shape.

Notes

  • 8 / 11 (helpers + labels) — wired up qdrant.fullname (its use in alerts.yaml is the fix for add helm charts for services and cronjobs #1), and flattened qdrant.selectorLabels to the immutable-safe app-only set, dropping the mixed app.kubernetes.io/part-of. I intentionally did not add the upstream .labels/.chart helpers, since those reintroduce the app.kubernetes.io/* keys your note 11 flags — keeping the flat datastore convention instead. Settled now, pre-release, so no StatefulSet-selector migration for anyone.
  • 9appVersion: "1.19.0".
  • 12 — added templates/NOTES.txt with the API-key retrieval command, connection details, and the retained-PVC / new-key-on-reinstall caveat.
  • 10 — noted for the next PR (feature/ prefix).
  • 13 — the docs/src/js/config.js site drift is tracked in docs(site): helm.zop.dev renders integrations from a hardcoded config.js — new charts never appear #308, out of scope here.

Repackaged qdrant-v0.0.1.tgz and refreshed its index.yaml digest. helm lint clean; fresh-install re-test green (pod Ready 0 restarts, /metrics 401 without key / 200 with Bearer, collection create + search OK).

@arunesh-j arunesh-j 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.

Re-review of 6525d29

Re-ran the whole review against the new commit in a fresh worktree and reinstalled on minikube. All three blocking findings are fixed, and I confirmed the two that were testable rather than taking the diff's word for it.

One thing came back in a new file — details inline.

Blocking — all resolved

# Finding Status
1 PrometheusRule collision Fixed & verified
2 Off-domain 404 icon Fixed
3 Undeclared CRD prerequisite Fixed

On (1), the umbrella chart that previously failed now installs cleanly, and both rules coexist:

STATUS: deployed
$ kubectl get prometheusrule
myapp            <- postgres
myapp-qdrant     <- qdrant

On (3), this was the one claim I could not verify last time. The README now states it, which is what I asked for — still reasoned rather than tested, since the minikube still has the CRDs.

Should fix — resolved

  • /metrics authstatefulset.yaml and README.md:59 both corrected. Re-measured: /metrics401 without the key, 200 with.
  • Double scrape — the headless Service's new app: <release>-qdrant-headless label works. kubectl get svc -l app=r310b-qdrant now returns exactly one Service, and the pod selector is untouched so DNS identity still works.
  • Dead alertQdrantInRecoveryMode replaces it. I confirmed app_status_recovery_mode is really exported (app_status_recovery_mode 0) and the CRD accepted the rule. QdrantDown at for: 2m is the right call.
  • Schema descriptionsverify-values.py now reports 0 blocking, 0 should-fix (was 1 should-fix).

Notes — resolved

appVersion: "1.19.0", qdrant.fullname now used, app.kubernetes.io/part-of dropped from the immutable selector, NOTES.txt added.

Nice touch on the PVC docs — I checked the command you documented actually works, which was not a given: the StatefulSet controller copies the selector label onto the volumeClaimTemplate PVC, so kubectl delete pvc -l app=<release>-qdrant really does match.

One remaining should-fix

templates/NOTES.txt:11 reintroduces the exact /metrics claim that was just fixed in the other two places. Commented inline.

Verified on this commit

  • helm lint → 0 failed. Icon returns 200, and the repo-wide off-domain audit is clean.
  • docs/index.yaml: added: 1, removed: 0, changed: 0 — the repackaged tarball unpacks to exactly the current source (NOTES.txt included), digest matches, v prefix intact. Repackaging v0.0.1 is safe here since it has never been published to main.
  • Cluster: Ready with 0 restarts; helm upgrade preserved the API key byte-for-byte and the test collection survived.

Nothing blocking from me now. I'm leaving my earlier CHANGES_REQUESTED in place rather than clearing it myself — that call is @zopdev's, and the NOTES.txt line above is a one-liner if you want it gone first.

Comment thread charts/qdrant/templates/NOTES.txt Outdated
Comment on lines +11 to +12
The health endpoints and /metrics stay reachable, but all data
operations are rejected without it.

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.

Should fix — this is the /metrics claim from the last round, back in a new file.

README.md:59 and the statefulset.yaml comment were both corrected in this commit, but the new NOTES.txt repeats the original error. Re-measured against this build:

/metrics no key   -> 401
/metrics with key -> 200

/metrics does not stay reachable — only /livez, /readyz and /healthz do. This is the text an operator sees at install time, so it is the most likely of the three to be believed.

Suggested change
The health endpoints and /metrics stay reachable, but all data
operations are rejected without it.
Pass it in the "api-key" header (or "Authorization: Bearer <key>").
Only the health endpoints (/livez, /readyz, /healthz) are open — every
other request, including /metrics, is rejected without the key.

@arunesh-j arunesh-j 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.

Correction to my re-review — the icon is not properly resolved

In the re-review above I marked the icon finding Fixed. That was wrong, and I want to correct it before it ships.

https://zop.dev/logo.png is on the allowed list and returns 200, which is what I checked. What I did not check is whether that generic logo is appropriate for a chart of this type — and in this repo it never is. Details inline on Chart.yaml.

The original problem is genuinely gone: the icon is no longer off-domain and no longer 404s, so the supply-chain and broken-image concerns are resolved. What replaced it is a placeholder, and qdrant is a datasource chart that will render in the zop.dev Datasources catalog.

Downgrading this from blocking to should fix — it installs fine and carries no risk — but it does need the real asset, which only someone with bucket access can upload.

Comment thread charts/qdrant/Chart.yaml Outdated
description: Helm chart for deploying qdrant vector database datastore
name: qdrant
version: 0.0.1
icon: "https://zop.dev/logo.png"

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.

Should fix — this is the generic zop.dev mark, not the Qdrant logo, and this chart is type: datasource.

Correcting myself here: I accepted this in my re-review because it is on the allowed list and returns 200. But I checked the wrong thing. Auditing all 33 charts against their annotations.type:

Chart type Own product logo Generic zop.dev/logo.png
datasource (15) 15 0
application (8) 8 0
untyped building blocks (10) 2 5

The correlation is exact. The only five charts using the generic logo — cron-job, scylladb, service, uptime-monitoring, zookeeper — are all untyped, so they never appear in the zop.dev listing at all. Every chart that carries an annotations.type ships its own product logo, with no exceptions.

Chart.yaml:9 sets type: datasource, so qdrant lands in the Datasources catalog next to postgres, redis, kafka and clickhouse — each showing its real logo — while qdrant shows a generic zop.dev mark. Qdrant has a perfectly good logo of its own; this is the case the icon: field exists for.

Fix: upload the Qdrant logo to storage.googleapis.com/zopdev-blog-resources/ — the bucket clickhouse, litellm, localai, holmesgpt and n8n use — and point icon: at it. I cannot upload to that bucket, so this one needs you.

Everything else in 6525d29 still checks out; this is the only item I got wrong.

- icon: point to the Qdrant logo in the zopdev-blog-resources bucket, matching
  the datasource-chart convention (every typed chart ships its own product logo)
- NOTES.txt: correct the auth note — /metrics is not open; only the health
  endpoints (/livez, /readyz, /healthz) are reachable without the key
- repackage qdrant-v0.0.1.tgz and refresh its index digest
@NitinKumar004

Copy link
Copy Markdown
Contributor Author

@arunesh-j thanks for the second pass — both remaining items are addressed in 1f18326.

  • NOTES.txt:11 /metrics claim — fixed with your suggested wording; it now reads "Only the health endpoints (/livez, /readyz, /healthz) are open — every other request, including /metrics, is rejected without the key." That was the third and last place the stale claim was hiding.
  • Icon — replaced the generic mark with the actual Qdrant logo, now uploaded to the zopdev-blog-resources bucket the other typed charts use: https://storage.googleapis.com/zopdev-blog-resources/1/files/originals/20260813/a95c34e7-1888-4a94-8476-0ea91d710f53-qdrant.png (returns 200). Good catch on the type-vs-logo correlation — qdrant lands in the Datasources catalog, so it should carry its own logo like postgres/redis/kafka/clickhouse.

Repackaged qdrant-v0.0.1.tgz and refreshed its index.yaml digest (only the qdrant entry changed). helm lint clean. Ready for another look when you have a moment.

@arunesh-j arunesh-j 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.

Re-review of 1f18326 — everything I raised is now resolved

Both remaining items are fixed, and I verified each rather than reading the diff.

Icon

icon: now points at zopdev-blog-resources/.../qdrant.png. I fetched it and looked at the image itself rather than trusting the filename — it is the genuine Qdrant mark (the isometric red q cube), a 600×600 PNG, HTTP/2 200, content-type: image/png.

That also puts qdrant on the newer bucket alongside clickhouse, litellm, localai, holmesgpt and n8n, and restores the invariant that made me reopen this: every chart carrying an annotations.type ships its own product logo. The repo-wide off-domain audit is clean.

NOTES.txt

The corrected text renders on a live install, and I re-measured every endpoint it names to confirm the text is now true rather than merely different:

Endpoint No key NOTES claims
/livez 200 open ✓
/readyz 200 open ✓
/healthz 200 open ✓
/metrics 401 rejected ✓

/metrics with the key returns 200. All three places that describe this — README.md, the statefulset.yaml comment, and now NOTES.txt — finally agree with each other and with the server.

Re-verified on this commit

  • helm lint → 0 failed; verify-values.py0 blocking, 0 should-fix.
  • docs/index.yaml: added: 1, removed: 0, changed: 0. The repackaged tarball unpacks to exactly the current source, digest matches, v prefix intact, and the packaged NOTES.txt carries the corrected wording.
  • Umbrella install with qdrant + postgres: 0 duplicate object names — the PrometheusRule collision stays fixed.
  • Cluster: Ready with 0 restarts; ServiceMonitor still selects exactly 1 Service, so no double scrape.

Summary across the three rounds

Severity Raised Open
Blocking 3 0
Should fix 6 0
Note 4 0

Nothing outstanding from me. Two things worth recording, neither a change request:

  • The _helpers.tpl set still omits .name, .chart and .labels. Several datastore charts in the repo are the same, so this is consistent with where the chart lives — noting it only so it is a deliberate choice rather than an oversight.
  • helm.zop.dev renders from the hardcoded list in docs/src/js/config.js, so qdrant will not appear on the site after merge until that list is updated (repo-wide drift, issue #308) — worth a follow-up now that the chart has a proper logo to show.

Nice work turning these around. I am not approving — my earlier CHANGES_REQUESTED is still what is blocking the merge, and clearing it is @zopdev's call, not mine. From a review standpoint there is nothing left to fix.

@arunesh-j arunesh-j 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.

Three publishing items — plus a correction to something I told you earlier

Re-ran the review against 32c7f71c. The chart itself is still clean; these are all about publishing, and I should have caught the first two in round one.

First, a correction

In earlier rounds I told you qdrant "will not appear on helm.zop.dev until docs/src/js/config.js is updated (issue #308)". That is no longer true — the main you just merged replaced config.js with helm-index.js, and the site now builds its grid from docs/index.yaml. Nothing needs adding to a site list. Sorry for the noise; the merge changed the answer under me, and it makes item 3 below relevant instead.

1. docs/index.yaml was hand-edited, not regenerated — should fix

CONTRIBUTING Step 6 requires cd docs && helm repo index . --url https://helm.zop.dev, which rewrites generated: and every created: timestamp. This PR shows neither:

Signal This PR Every other chart PR
docs/index.yaml diff +16 −0 ~+190 −175
generated: changed no — byte-identical to main yes
created: rewritten 0 of 180 all 180

I ran Step 6 locally: it produces 366 changed lines, 362 of them created:. The last 12 commits touching this file — 5f318dd, ff3ad93, f9b595b, 4512540, 9691853, 72cd8f3, d19c5d8 and back — all show the full churn. This PR is the only exception.

The entry itself is correct — I diffed it field-by-field against generated output: digest verified against the tarball, urls, annotations, appVersion and alphabetical position all right. The only mismatch is created:, which reads 2026-08-12T15:56:00.000000+05:30 (round seconds, zero microseconds; helm emits values like 15:41:22.241313).

So nothing is broken today. It matters because the digest is 64 hand-copied hex characters — right this time, with nothing but the tool guaranteeing the next one, and a wrong digest fails at helm install on a user's machine rather than in CI. Detail inline.

2. Root README.md chart table is missing qdrant — should fix

Available Charts1. DATASOURCES lists 15 charts; the repo has 16 with annotations.type: datasource. qdrant is the only one absent, and the table is otherwise exhaustive:

| **Qdrant**           | [helm.zop.dev/qdrant](https://helm.zop.dev/qdrant)                     ||

The ✅ is right — the chart ships a ServiceMonitor and exports Prometheus metrics.

In fairness: CONTRIBUTING never mentions the root README, so this is an unwritten convention rather than a documented step you skipped. Precedent is nonetheless consistent — holmesgpt (#307), litellm and clickhouse (#287) all updated it in the same PR. Might be worth a Step 7 in CONTRIBUTING so the next contributor is not left to infer it.

3. description: is now the site card text — should fix

This one only became a finding with the merge. Per the new CONTRIBUTING section Showing up on helm.zop.dev, Chart.yaml's description is what renders on the integrations grid, and it should be written "for a reader who is choosing an integration".

Yours currently reads:

Helm chart for deploying qdrant vector database datastore

That is chart-implementation copy, and it lowercases the brand mid-sentence. On the grid it will sit beside:

  • ChromaDB — The AI-native embedding database
  • ClickHouse — Column-oriented database for real-time analytics at scale

Something like "High-performance vector database for AI search, recommendations and RAG" would match. You do not need a docs/src/js/display.js entry — I checked the fallback and qdrant title-cases to Qdrant correctly on its own.


All three are should fix, none blocking, and the chart itself is unchanged from my last review: 0 open findings on the templates, schema, and cluster behaviour.

Comment thread docs/index.yaml
urls:
- https://helm.zop.dev/postgres-v0.0.1.tgz
version: v0.0.1
qdrant:

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.

Should fix — this entry was written by hand; CONTRIBUTING Step 6 was not run.

The block is correct — I diffed it field-by-field against what helm repo index generates and everything matches, digest included. But the file shows the command was never executed:

  • generated: at the top of the file is byte-identical to main (2026-08-11T21:22:14.966412+05:30). helm repo index always rewrites it.
  • 0 of 180 existing created: timestamps changed; the command rewrites every one.
  • The whole diff is +16 −0. Running Step 6 here produces 366 changed lines.

The created: value below is a further giveaway — 2026-08-12T15:56:00.000000+05:30 has round seconds and zero microseconds, where helm emits e.g. 15:41:22.241313.

Fix:

cd docs && helm repo index . --url https://helm.zop.dev

Expect the diff to grow to ~370 lines. That is normal here — every other chart PR in this repo looks like that. (--merge is not a shortcut; it rewrites the timestamps too.)

- docs/index.yaml: regenerate with 'helm repo index' (CONTRIBUTING Step 6)
  instead of a hand-edited entry, so the digest is tool-computed
- Chart.yaml: rewrite description as site-card copy — 'High-performance vector
  database for AI search, recommendations and RAG' (was implementation text that
  lowercased the brand); repackage v0.0.1
- README.md: add Qdrant to the DATASOURCES table
@NitinKumar004

Copy link
Copy Markdown
Contributor Author

@arunesh-j all three publishing items addressed in 339658b — and no worries on the #308/#309 correction, the merge genuinely changed the answer.

  1. index.yaml regenerated, not hand-edited — ran CONTRIBUTING Step 6 (cd docs && helm repo index . --url https://helm.zop.dev). generated: and all 180 created: timestamps are now rewritten by the tool, and the qdrant digest is tool-computed rather than 64 hand-copied hex chars. Diff is now the expected ~371 lines; verified the full chart list is unchanged (32 entries, none dropped/reordered).
  2. Root README table — added the Qdrant row under 1. DATASOURCES with the ✅ metrics mark (it ships a ServiceMonitor).
  3. description as card text — rewrote it to "High-performance vector database for AI search, recommendations and RAG" (was implementation copy that lowercased the brand). Repackaged v0.0.1 so the new description reaches index.yaml; confirmed qdrant title-cases to Qdrant from the fallback, so no display.js entry needed.

helm lint clean, v0.0.1 still unpublished so repackaging is safe. Ready for another look.

@arunesh-j

Copy link
Copy Markdown
Contributor

Review — feat(qdrant): add Qdrant vector database chart

Reviewed at 715630a in an isolated worktree. Findings are 3 notes, nothing blocking. I checked the load-bearing claims on a cluster rather than reading for them; what was verified versus merely read is separated at the end.

Check Result
CI reproduction (helm dependency update + helm lint) clean
Defaults render clean
Icon on a zopdev-controlled bucket, and resolves storage.googleapis.com/zopdev-blog-resources/… → HTTP 200
annotations.type datasource
Schema checker (categories, mutable, defaults, descriptions) 0 blocking, 0 should-fix
index.yaml isolation added: 1 (qdrant v0.0.1), removed: 0, changed: 0; 183 digests verified
index.yaml genuinely regenerated 0/182 created: timestamps carried → regenerated, generated: touched twice, diff +200/−183
v-prefixed tarball, version matches Chart.yaml, no committed subchart tarball
PrometheusRule name collision trap avoided — named <release>-qdrant, not {{ .Release.Name }}, with a comment saying why
Duplicate kind+name in the release none
Root README DATASOURCES table Qdrant listed
Install on minikube Ready, 0 restarts, clean logs
Upgrade ×2 clean; API key preserved via lookup

Several things here are better than the repo's average and worth saying so:

  • The CRD prerequisite is documented with the exact failure text. ServiceMonitor/PrometheusRule render unconditionally — the norm for datasource charts here (redis, postgres, mysql, mariadb, kafka, clickhouse, solr, solrcloud all do the same) — but this is the first chart I've seen state it as a hard prerequisite and quote no matches for kind "ServiceMonitor". That is exactly the honest-prerequisites bar; other charts silently send people into a failed install.
  • The non-root story is complete rather than aspirational. runAsNonRoot + fsGroup: 3000 for the provisioned volume, and — the part that is usually missed — QDRANT__STORAGE__SNAPSHOTS_PATH and QDRANT_INIT_FILE_PATH redirected onto the PVC because the image's own directories are root-owned.
  • The ServiceMonitor's Bearer credentials point at a secret that actually exists. Rendered: credentials.name: t-qdrant-apikey-secret, key: api-key, matching the rendered Secret exactly. Easy thing to get subtly wrong; it is right.
  • NOTES.txt covers the trap I went looking for: uninstall deletes the Secret but retains the PVC, so a reinstall against the retained volume comes up with a new key.

1. [Note] The chart is single-node, and nothing says so

templates/statefulset.yaml:15 hardcodes replicas: 1, there is no scaling value, and Qdrant logs Distributed mode disabled on boot. README.md and values.yaml never mention it.

Readers arriving from the sibling datastores will assume otherwise — charts/postgres exposes replication.enabled/replication.count, so the absence of any such key here reads as an oversight rather than a decision. One line in the README (and/or a comment in values.yaml) saying the chart runs a single node with no HA/distributed-mode support would settle it, and would also tell a reader evaluating this for production what they are choosing.

2. [Note] appVersion can silently drift from the image actually deployed

  • Chart.yaml:2appVersion: "1.19.0"
  • values.yaml:2version: "v1.19.0" (the image tag)
  • templates/statefulset.yaml:37image: qdrant/qdrant:{{ .Values.version }}

The image tag comes from .Values.version, and appVersion feeds nothing. They also differ in form (v prefix), so they cannot be compared at a glance. Bump one and forget the other and the chart advertises a version it is not running — on helm.zop.dev and in helm list.

Both spellings are correct in isolation (Qdrant's tags are v-prefixed), so this is only about keeping them in step. Either derive the default — qdrant/qdrant:{{ .Values.version | default (printf "v%s" .Chart.AppVersion) }} — or leave a comment at both sites noting they must move together.

3. [Note, trivial] livenessProbe.initialDelaySeconds: 30 is inert

templates/statefulset.yaml:66-71. A startupProbe is defined, and the kubelet does not run liveness or readiness until the startup probe first succeeds — so the 30s delay never applies. Harmless, but it reads as protection that is not doing anything. Dropping it makes the probe block say what it means.


What I verified versus what I read

Verified on minikube (fresh namespace, per-PR release name, explicit --context on every call): install reached Ready with 0 restarts and no warnings in the logs; /metrics401 without the key and 200 with it; /livez, /readyz, /healthz200 unauthenticated, so the probes work as claimed; /collections → 401; two helm upgrades clean, with the API key byte-identical before and after; --set customConfig='log_level: DEBUG' mounted at /qdrant/config/local.yaml and Qdrant emitted DEBUG lines, so the subPath mount and the reloader annotation target are correct; the plain ConfigMap carries only QDRANT_HOST/QDRANT_HTTP_PORT/QDRANT_GRPC_PORT — nothing sensitive.

Read, not executed: the alert rule expressions themselves (QdrantDown) were not fired against a live Prometheus, and Reloader is not installed on my test cluster, so the annotation's target was verified but not an actual reload.

Two things I checked and specifically am not reporting, since verification showed they would have been unfair: the ungated CRD objects (the established datasource pattern here, and honestly documented), and the feat/qdrant branch name against CONTRIBUTING §2's feature/|fix/|docs/ — merged history shows service-cronjob-v0.0.3, chart-mysql-v0.0.1 and similar, so that convention is not actually in force.

…robe cleanup

- README/values.yaml: state the chart is single-node (replicas: 1, no HA)
- Chart.yaml/values.yaml: comment that appVersion and the image tag (version)
  must move together
- statefulset: drop inert initialDelaySeconds from liveness/readiness probes —
  the startupProbe already gates both
- repackage v0.0.1 and regenerate docs/index.yaml
@NitinKumar004

Copy link
Copy Markdown
Contributor Author

@arunesh-j thanks — all three notes addressed in 29efec9.

  1. Single-node is now stated. Added a callout to the chart README ("This chart deploys one Qdrant node (replicas: 1, distributed mode off) — no HA/clustering, intended as an application datastore") and a matching comment in values.yaml.
  2. appVersion / image-tag drift — added a comment at both sites (Chart.yaml appVersion and values.yaml version) noting they must move together, and that appVersion is what shows on helm.zop.dev / helm list. I skipped the | default (printf "v%s" .Chart.AppVersion) derive: since version always carries an explicit default, that branch never fires — it'd be exactly the kind of inert code note 3 is about.
  3. Inert initialDelaySeconds — dropped from both liveness and readiness (your reasoning covers both, since the startupProbe gates them), with a comment saying why.

Repackaged v0.0.1 and regenerated docs/index.yaml (Step 6); helm lint clean, chart list unchanged. Ready for another look.

@arunesh-j arunesh-j 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.

Re-review of 29efec9

All three notes resolved. Nothing new, and nothing outstanding from my side.

Note Shipped Verified
1. Single-node undocumented README callout at line 5 — immediately under the intro, not buried — plus a matching comment in values.yaml
2. appVersion / image-tag drift Comments at both sites; the Chart.yaml one also says appVersion is what shows on helm.zop.dev and helm list
3. Inert initialDelaySeconds Removed from liveness and readiness, with a comment explaining the startupProbe gates both ✓ rendered pod has neither field; startupProbe still 30×10s

On skipping the | default (printf "v%s" .Chart.AppVersion) derive — you're right, and for the right reason. values.yaml always ships an explicit version, so that branch could only fire if someone set version: ""; it would be exactly the inert-config pattern note 3 was about. My note offered "derive or comment at both sites", and the comments are the better half of that choice here. Extending note 3's reasoning to readinessProbe was also correct — the startup probe gates readiness identically, so that delay was equally dead.

Re-ran, not assumed

  • helm dependency update + helm lint: clean. Defaults render. Schema checker: 0 blocking, 0 should-fix.
  • Cluster: fresh namespace on minikube, helm install --wait returned in 12s, pod Ready with 0 restarts. Rendered probe spec confirms initialDelaySeconds is gone from both and the startup budget is unchanged — so removing it did not cost you readiness responsiveness. Torn down afterwards.
  • Publishing: 183 tarball digests verified; added: 1 (qdrant v0.0.1), removed: 0, changed: 0; index regenerated properly again (0/182 created: timestamps carried). Packaged tarball matches source apart from helm's own Chart.yaml reserialization.
  • On repackaging v0.0.1 rather than bumping: correct here, and I checked rather than assumed — qdrant is absent from origin/main's index and helm.zop.dev serves no qdrant-v0.0.1.tgz, so nothing live can be broken by replacing that artifact. The rule against republishing a version with different content only bites once the version has actually shipped.

Not re-verified this round (unchanged since the first pass, and outside 29efec9's diff): the API-key auth behaviour (/metrics 401 → 200, health endpoints open), key preservation across upgrade, and the customConfig subPath mount. All three were confirmed on a cluster during the first review.

Looks good to me.

@jatintalgotra-zd jatintalgotra-zd 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.

Independent pass on 29efec9 — 3 findings

Reviewed in a separate worktree and installed on minikube with the full kube-prometheus-stack rather than just the CRDs, so the ServiceMonitor path could actually be exercised end to end. The chart is in good shape and most of the load-bearing claims hold up under test — what I confirmed working is listed at the bottom.

Three things still look open to me. I traced each to primary sources (Qdrant's own source, the official docs, the official qdrant-helm chart) rather than reasoning from the diff, and reproduced the two behavioural ones on the cluster.


1. QdrantInRecoveryMode cannot fire as the chart ships — templates/alerts.yaml:32-36

The alert fires on app_status_recovery_mode == 1. Nothing in this chart can produce that value. The chain, closed end to end in the Qdrant tree:

Step Evidence
The image runs the wrapper script Dockerfile:246CMD ["./entrypoint.sh"]
Its recovery branch is the only injector in the image tools/entrypoint.sh:52QDRANT__STORAGE__RECOVERY_MODE="$RECOVERY_MESSAGE" ./qdrant $@
…and it is gated off by default tools/entrypoint.sh:29QDRANT_ALLOW_RECOVERY_MODE=${QDRANT_ALLOW_RECOVERY_MODE:-false}
tools/entrypoint.sh:32if [ "$QDRANT_ALLOW_RECOVERY_MODE" != true ]; then exit $EXIT_CODE; fi
No default in shipped config recovery_mode appears in neither config/config.yaml nor config/production.yaml, so Option<String> stays None (lib/storage/src/types.rs:127)
The metric derives from nothing else src/common/telemetry_ops/app_telemetry.rs:117recovery_mode: settings.storage.recovery_mode.is_some()
src/common/metrics.rs:235gauge(if self.recovery_mode { 1.0 } else { 0.0 }, &[])
The chart never sets the gate grep -rn ALLOW_RECOVERY_MODE charts/qdrant/ → no matches; rendered env is only QDRANT__SERVICE__API_KEY, QDRANT__STORAGE__SNAPSHOTS_PATH, QDRANT_INIT_FILE_PATH

A grep of the whole Qdrant source shows no other writer of recovery_mode — no API, no flag.

Reproduced. Installed with resources.limits.memory=16Mi to force the OOM this alert exists for:

lastState: terminated{ exitCode: 137, reason: OOMKilled }
./entrypoint.sh: line 25:  7 Killed  ./qdrant $@
r3-qdrant-0   0/1   CrashLoopBackOff   3 (30s ago)

Straight to CrashLoopBackOff, no recovery attempt. And the alert's expression is fine — forcing storage.recovery_mode through customConfig gave me app_status_recovery_mode 1 and the rule loaded health: ok. The trigger is the only missing piece.

Worth noting this is the same class of problem as the QdrantHighRestResponseErrors finding from an earlier round, so it may be worth a rule of thumb: an alert ships with a reproduction of the state it fires on.

Suggested fix — one line, which resolves three things at once:

- name: QDRANT_ALLOW_RECOVERY_MODE
  value: "true"

That makes an OOM boot into recovery mode (where collections can be dropped to get back under the limit — the remedy Qdrant designed for this) instead of crashlooping; it gives QDRANT_INIT_FILE_PATH a consumer, since the entrypoint's init-file handshake is otherwise dead code in this chart; and it makes the alert reachable. Otherwise the honest move is to drop the alert.

The official chart corroborates that this is the intended knob — it is the one documented example env var in its values.yaml:14:

env: []
  # - name: QDRANT_ALLOW_RECOVERY_MODE
  #   value: true

(Upstream leaves it off, but upstream also ships no PrometheusRule at all, so it has nothing to be inconsistent with.)


2. Changing customConfig on helm upgrade is a silent no-op

Reproduced on the cluster. Installed with telemetry_disabled: true, then upgraded to telemetry_disabled: false, log_level: DEBUG:

upgraded
ConfigMap now holds: telemetry_disabled: false / log_level: DEBUG
sts generation: 1 -> 1          (no rollout triggered)
pod uid:  ce64f1ce-… -> ce64f1ce-…   (unchanged)
file inside the running pod: telemetry_disabled: true / log_level: INFO   <- stale

helm upgrade reports success, the ConfigMap updates, and nothing reaches the process. So customConfig is effectively write-only after install.

Two upstream confirmations of the mechanism:

  • The Kubernetes docs are explicit, and the chart's own comment at statefulset.yaml:8-9 already says as much: "A container using a ConfigMap as a subPath volume mount will not receive updates when the ConfigMap changes."
  • The Reloader annotation is correctly placed — Stakater's docs put it on the workload's metadata.annotations, not the pod template, and StatefulSet is a supported kind. statefulset.yaml:7-11 is right.

So this isn't a broken annotation. The gap is that the only mechanism is conditional on an operator that README.md:12 itself calls "(optional, recommended)", and the pod template (statefulset.yaml:24-26) carries labels only. On any cluster without Reloader — including a plain helm install from helm.zop.dev — the documented customConfig knob silently stops working after install.

Suggested fix — additive, keep the Reloader annotation and add a checksum to the pod template:

  template:
    metadata:
      annotations:
        checksum/custom-config: {{ include (print $.Template.BasePath "/custom-config-configmap.yaml") . | sha256sum }}

This is what the official Qdrant chart does (qdrant-helm/charts/qdrant/templates/statefulset.yaml:29-31, checksum/config + checksum/secret), and it is already the idiom in four charts here — holmesgpt, litellm, opentsdb, openobserve-standalone. It needs no cluster add-ons.

clickhouse has the same gap, but qdrant is unreleased, so fixing it here costs nothing.


3. Telemetry is on by default, while chromadb turns it off — judgment call

Confirmed on a default install:

INFO qdrant: Telemetry reporting enabled, id: fb734b81-e3e6-4082-b454-b1731365fc10

From the source: config/config.yaml:433 sets telemetry_disabled: false; config/production.yaml (the RUN_MODE layer the image sets at Dockerfile:234) overrides only log_level and service, so it does not touch it; src/main.rs:390 gates on !settings.telemetry_disabled && !args.disable_telemetry; and src/common/telemetry_reporting.rs:41,60 POSTs to https://telemetry.qdrant.io on a REPORTING_INTERVAL of one hour.

I want to be fair about the severity here. The official docs say the payload is system info, timings/counters and error backtraces, and explicitly not IP addresses, identifying information, stored data, collection names or URLs — so it is anonymous, not a data leak. And the official Qdrant chart does not disable it either. So this is not an upstream-practice divergence, and I would not call it a defect.

What does stand is the repo's own convention for this exact class of chart: charts/chromadb/templates/configmap.yaml:10 sets ANONYMIZED_TELEMETRY: "False", and it is load-bearing — consumed at charts/chromadb/templates/statefulset.yaml:22-24 via envFrom.configMapRef. Since this PR positions qdrant as the production counterpart to chromadb, it seems worth the two chart's telemetry postures matching, whichever way you pick. Hourly egress to a third-party endpoint from a customer namespace is also the kind of thing that trips egress policy.

If you want it off, the official docs name the exact env var (ops-configuration/usage-statistics/):

- name: QDRANT__TELEMETRY_DISABLED
  value: "true"

I checked precedence rather than assuming it: with that env set and telemetry_disabled: false in the mounted customConfig, the log flips to Telemetry reporting disabled. Env wins, consistent with values.yaml:23-24. Note that also makes it non-overridable via customConfig, same as the API key — which I think is the behaviour you want.

Entirely your call — flagging it as a consistency decision to make deliberately rather than something to fix.


Verified working (not read — measured)

Listing these so the findings above aren't mistaken for a poor overall impression.

  • Install clean: 22s, pod 1/1, 0 restarts, non-root uid=1000 gid=2000 groups=2000,3000.
  • Auth matrix matches all three places that document it — /livez /readyz /healthz → 200 with no key; /metrics /collections /telemetry401 without, 200 with either api-key or Authorization: Bearer.
  • The ServiceMonitor Bearer auth genuinely works. Prometheus target health: up, lastError: '', and up{namespace="qd",service="r1-qdrant"} = 1 — so the label selectors in the alert expressions are correct. Exactly one target, confirming the headless-Service relabel fixed the double scrape. Both rules loaded health: ok.
  • Snapshots and init file correctly relocated onto the PVC. I also checked that this doesn't defeat the entrypoint's OOM heuristic: src/startup.rs calls remove_started_file_indicator() before initialization ("Use before server initialization to avoid false positives"), so persisting the marker is right.
  • Data plane: collection create, upsert and vector search all correct; points_count: 2 survives kubectl delete pod.
  • API key preserved across helm upgrade; pattern matches postgres exactly.
  • Umbrella install with postgres in one release works — myapp and myapp-qdrant PrometheusRules coexist, myapp-postgres and myapp-qdrant ServiceMonitors coexist. The original blocking collision is genuinely gone.
  • PVC carries app=<release>-qdrant, so the README's kubectl delete pvc -l app=… actually works.
  • The README's CRD prerequisite reproduces verbatim, including the quoted error text.
  • Uninstall clean; only the PVC remains, which both README.md and NOTES.txt document.
  • Publishing: docs/index.yaml shows qdrant added, 0 removed, 0 changed, and all 183 digests in the index match their tarballs on disk. v-prefixed tarball matches repo convention.
  • Conventions all line up with siblings: mutable + editDisabled on diskSize, pattern on cpu/memory, generation: 1, ServiceMonitor without namespace, flat app selector label, PVC template naming, startupProbe 30×10s (identical to chromadb), the lookup secret pattern, root README row, annotations.type: datasource, icon on the zopdev bucket. On hardening it is ahead of the repo — almost no other datastore chart here sets runAsNonRoot.

Nice chart overall. (2) is the one I'd treat as blocking, since a documented knob that silently stops working generates support load; (1) is a small change with a real payoff; (3) is yours to decide.

- statefulset: set QDRANT_ALLOW_RECOVERY_MODE=true so an OOM boots into recovery
  mode (drop a collection to recover) instead of crash-looping, and so the
  QdrantInRecoveryMode alert is actually reachable
- statefulset: add a checksum/custom-config pod-template annotation so changing
  customConfig on 'helm upgrade' rolls the pod even without Reloader installed
  (subPath mounts don't live-update); verified the pod rolls and picks up config
- statefulset: set QDRANT__TELEMETRY_DISABLED=true to match the chromadb chart
  and avoid third-party egress from customer namespaces
- repackage v0.0.1 and regenerate docs/index.yaml
@NitinKumar004

Copy link
Copy Markdown
Contributor Author

@jatintalgotra-zd excellent review — the source-traced findings were exactly right. All three addressed in 2d33b2e and re-verified on minikube.

1. QdrantInRecoveryMode unreachable → set QDRANT_ALLOW_RECOVERY_MODE=true

Added the env var. As you noted, this does three things at once: an OOM now boots into recovery mode (drop a collection to recover) instead of crash-looping, the QDRANT_INIT_FILE_PATH handshake gets a consumer, and the alert becomes reachable. Confirmed present in the running pod.

2. customConfig upgrade was a silent no-op → checksum on the pod template (the blocking one)

Added checksum/custom-config to spec.template.metadata.annotations (kept the Reloader annotation too). Reproduced your exact scenario without Reloader installed:

before:  pod uid=188c9540…  sts gen=1
upgrade customConfig -> log_level: DEBUG
after:   pod uid=22dcd6d1…  sts gen=2   (pod ROLLED)
running process now emits DEBUG lines   (new config reached it)

So the documented knob now actually takes effect on upgrade, no add-on required — matching the official chart and the four sibling charts you cited.

3. Telemetry → disabled, matching chromadb

Set QDRANT__TELEMETRY_DISABLED=true. Went with off for consistency with chromadb and to avoid the hourly third-party egress from a customer namespace. Verified env precedence the way you did — installed with telemetry_disabled: false in customConfig and the log still reads Telemetry reporting disabled, so it's not overridable via customConfig, same as the API key.

Repackaged v0.0.1, regenerated docs/index.yaml (Step 6), helm lint clean. Thanks for testing against the full kube-prometheus-stack — the ServiceMonitor confirmation was reassuring to see. Ready for another look.

@jatintalgotra-zd jatintalgotra-zd 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.

Re-review of 2d33b2e — all three fixed correctly; one new trade-off, and one older item

Re-ran everything on a fresh minikube with the full kube-prometheus-stack. The diff is exactly the three changes and nothing else, and all three do what they claim. Two things I'd still like to raise — the first is a consequence of the fix I asked for, so it's on me for not thinking it through in the original comment.

The three fixes — verified

customConfig rollout (the blocking one) — measured with no Reloader on the cluster:

Scenario Result
config A → B checksum changed, sts generation 1→2, pod rolled, running pod holds config B, and DEBUG lines in the log prove it reached the process
re-upgrade, same values pod stable — checksum is deterministic, so no restart churn on every upgrade
customConfig removed annotation, volume and ConfigMap all cleanly removed, pod rolled
default release no checksum annotation — the if .Values.customConfig gate works

One correction to something I nearly reported as a bug: my first "removal" test looked like the ConfigMap was lingering. That was my error — helm upgrade with no value flags reuses the previous release's values, so customConfig was never actually cleared. With --reset-values the teardown is clean. The chart is right; my test was wrong.

Telemetry — log reads Telemetry reporting disabled. I specifically checked the regression I was worried about, since /metrics and app_status_recovery_mode both come off the telemetry collector: /metrics still 200 with 60 metric lines, app_info and app_status_recovery_mode both still exported, /telemetry still 200, Prometheus target health: up / lastError: '' / up = 1, one target per release. Nothing broke — consistent with main.rs:405,689, where reporting_enabled gates only the panic hook and the reporter spawn, not the collector.

Recovery modeQDRANT_ALLOW_RECOVERY_MODE=true present in the rendered env, and QdrantInRecoveryMode now genuinely reaches state: pending with the gauge at 1. No longer inert.


1. Recovery mode is a silent-degradation state, and warning now under-calls it

My original comment argued for QDRANT_ALLOW_RECOVERY_MODE=true and stopped there. I should have followed the consequence through to the alert, because the change moves the OOM outcome from loud to quiet.

I seeded a collection, then booted into recovery mode with data already on disk — the realistic post-OOM shape, rather than the empty-PVC case:

Signal Value
pod Ready True
restarts 0
in Service endpoints yes — traffic routes to it
/readyz 200
GET /collections/docs 500
search on existing data 400"Failed to search, Qdrant was killed during initialization. Most likely it's Out-of-Memory."
write to existing collection 400
DELETE /collections/docs 200 — the documented remedy does work
up 1, so QdrantDown (critical) does not fire
only alert that fires QdrantInRecoveryMode, severity: warning

Before this change, an OOM produced CrashLoopBackOff: pod not Ready, out of the Service, QdrantDown firing critical. After it, the database sits Ready, stays in the Service, accepts connections, and fails every read and write against existing collections — with a warning as the only signal.

The env var is still the right call; recovery mode is upstream's designed remedy and being able to DELETE a collection without fighting a crashlooping pod is worth having. It's the alert that no longer matches the state.

Suggested changealerts.yaml, raise it and say what it actually means:

        - alert: QdrantInRecoveryMode
          expr: app_status_recovery_mode{namespace="…", service="…"} == 1
          for: 2m
          labels:
            severity: critical
          annotations:
            summary: 'Qdrant instance {{ .Release.Name }} is in recovery mode'
            description: 'Qdrant instance {{ .Release.Name }} booted into recovery mode. It reports Ready and stays in the Service, but every read and write against existing collections fails. Drop a collection to recover, or raise the memory limit.'

This matches the repo's own severity convention rather than inventing one: warning is used for capacity and latency (ClickHouseTooManyConnections, MariaDBSlowQueries, KafkaConsumerGroupLag, KafkaUnderReplicatedPartitions), while up-but-broken states are criticalKafkaOfflinePartitions and MariaDBReplicationNotRunning are the closest analogues and both sit at critical.

Worth a line in the README's Monitoring section too, since "Ready but refusing all work" is surprising behaviour to meet for the first time during an incident.

To be explicit about what I'm not suggesting: the readiness probe is the wrong lever. /readyz returning 200 in recovery mode is Qdrant's own behaviour, and you want the pod reachable so an operator can drop the collection. Severity is the fix.


2. Release names ≥38 chars: helm install reports success but no pod is ever created

Not from this commit — it predates it, and I left it out of my first comment. Raising it now because it is free to fix only while the chart is unreleased.

Reproduced at a 47-char release name:

STATUS: deployed
sts:  <name>-qdrant   0/1          (forever)
pods: No resources found
event: spec.volumes[0].name: Invalid value:
       "<47-chars>-qdrant-persistent-storage": must be no more than 63 characters
leaked PVCs: 1

helm install exits 0, a PVC is provisioned and orphaned, and the only signal is a FailedCreate event. Helm allows release names up to 53 chars; at ≥48 it at least fails loudly on the headless Service name, but 38–47 is the silent window.

Cause is volumeClaimTemplates[].metadata.name: {{ .Release.Name }}-qdrant-persistent-storage, where the release name is redundant — volume names are pod-scoped, and the StatefulSet controller already appends the pod name to make the PVC unique.

  volumeClaimTemplates:
    - metadata:
        name: qdrant-storage

The official Qdrant chart doesn't put the release name in the volume name either, which is why it has no such threshold. clickhouse and postgres here share the bug (clickhouse's threshold is 33), but renaming those now would orphan live PVCs — qdrant is the one chart where it costs nothing. Entirely reasonable to defer as a follow-up if you'd rather keep this PR tight; it just gets more expensive after release.


Regression sweep — clean

helm lint clean. docs/qdrant-v0.0.1.tgz unpacks to exactly the current source (only the intentional v version prefix differs). docs/index.yaml: qdrant added, 0 removed, 0 changed, all 183 digests match their tarballs, genuinely regenerated (0 of 182 created: lines carried over), single tarball. Auth matrix unchanged. Data plane correct — create, upsert, search. API key preserved across upgrade. Umbrella install with postgres still coexists (myapp + myapp-qdrant rules, myapp-postgres + myapp-qdrant monitors).

Nice turnaround on all three — the checksum fix in particular is exactly the shape upstream uses.

Address the latest review (jatintalgotra-zd) on the qdrant chart:

- QdrantInRecoveryMode: raise severity warning -> critical and rewrite the
  description. Recovery mode is an up-but-broken state — the pod reports Ready
  and stays in the Service (so QdrantDown never fires), yet every read/write
  against existing collections fails. This matches the repo's own convention
  (KafkaOfflinePartitions, MariaDBReplicationNotRunning are critical). Document
  the behaviour in the README Monitoring section.

- volumeClaimTemplate: drop the redundant "{{ .Release.Name }}-qdrant-" prefix
  from the volume name (now "qdrant-storage"). Volume names are pod-scoped and
  the StatefulSet controller already makes the PVC unique; the prefix pushed the
  volume name past the 63-char limit on release names >=38 chars, where
  helm install reports success but no pod is ever created. The app selector
  label the controller copies onto the PVC still backs the documented
  `kubectl delete pvc -l app=<release>-qdrant`.

Repackaged docs/qdrant-v0.0.1.tgz and regenerated docs/index.yaml.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@NitinKumar004

Copy link
Copy Markdown
Contributor Author

@jatintalgotra-zd thanks for the deep, cluster-verified pass — both items are addressed in 487a965.

1. Recovery mode severity → critical (templates/alerts.yaml)

You are right that the QDRANT_ALLOW_RECOVERY_MODE=true fix moved the OOM outcome from loud to quiet, and warning under-called the result. QdrantInRecoveryMode is now severity: critical with your suggested description — it names the actual state (Ready + in-Service, but every read/write against existing collections fails, QdrantDown silent because up == 1) and the remedy (drop a collection, or raise resources.limits.memory). This lines up with the repo convention you cited: up-but-broken states (KafkaOfflinePartitions, MariaDBReplicationNotRunning) are critical, while capacity/latency stay warning. Also added a note to the README Monitoring section, since "Ready but refusing all work" is a surprising thing to meet mid-incident.

2. Release-name length / silent no-pod (templates/statefulset.yaml)

Fixed now while the chart is unreleased. volumeClaimTemplates[].metadata.name is just qdrant-storage — the release-name prefix was redundant (volume names are pod-scoped and the controller appends the pod name for uniqueness), and dropping it removes the 63-char threshold where helm install reports success but no pod is created. Matches the official qdrant-helm chart. The app selector label the controller copies onto the PVC is unchanged, so the documented kubectl delete pvc -l app=<release>-qdrant still works.

Repackaged docs/qdrant-v0.0.1.tgz and regenerated docs/index.yaml (only the qdrant digest changed as content; every other entry is just a fresh created: timestamp from helm repo index). helm lint clean; both alerts render critical and no persistent-storage references remain.

On the two recorded non-change-requests: leaving the flat _helpers.tpl set (.name/.chart/.labels omitted) as a deliberate match to the datastore family, and the docs/src/js/config.js site drift stays tracked in #308.

@jatintalgotra-zd jatintalgotra-zd 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.

Re-review of a02906e9 — both items fixed, approving

Re-ran the suite on a fresh minikube with the full kube-prometheus-stack. 487a965 is exactly the two changes asked for; a02906e9 merges main cleanly.

Recovery-mode alert — loaded in Prometheus as severity: critical, health: ok, with the new description intact. The README note is the useful half: "reports Ready and stays in the Service, but every read and write against existing collections fails" wasn't discoverable anywhere before.

Volume rename — the PVC no longer carries the release name, so the documented cleanup had to keep working off the label. It does: PVC qdrant-storage-z1-qdrant-0, labels {"app":"z1-qdrant"}, kubectl delete pvc -l app=z1-qdrant deletes it, data lands on the volume, points_count survives a pod delete.

Also confirmed the rename is safe rather than assuming it: v0.0.1 was never published — absent from the live index.yaml, tarball 404s. Renaming a volumeClaimTemplate in a released version would orphan live PVCs.

Silent window closed where it counts (plain helm install, no --wait):

Release name helm reports Pod
44, 45 STATUS: deployed Running
46, 47 STATUS: deployed none (+1 leaked PVC)
48+ INSTALLATION FAILED none — loud, no leak

38–45 now works; it used to fail silently across all of 38–47.

Two residual notes — neither worth another round

  1. 46–47 still fails silently, from a different limit: the controller-revision-hash label is <release>-qdrant + - + 10-char hash, so the StatefulSet name must be ≤52 → release ≤45. This still leaves qdrant ahead of its siblings (clickhouse's longer suffix caps it at 41). Recording the number rather than asking for a fix.
  2. Upgrading from a mid-review commit failsvolumeClaimTemplates is immutable, so 2d33b2ea02906e9 gives spec: Forbidden: updates to statefulset spec for fields other than 'replicas', … are forbidden. Uninstall/reinstall. Moot after merge; noted so nobody is surprised on their own cluster.

Merge + regressions

The merge brought only README changes from #319. The Qdrant row picked up main's new src/readme.html?id= link format and matches its neighbours, with no Deploy button — correct, that column is APPLICATIONS-only. A table reformat landing on a freshly-added row is a normal place for a merge to go quietly wrong, and this one didn't.

Sweep clean: helm lint passes; telemetry still disabled with all five env vars present; auth matrix intact; data plane correct; API key preserved on upgrade; customConfig checksum still rolls the pod; ServiceMonitor health: up, up = 1; tgz matches source; index.yaml shows qdrant added with 0 removed, 0 changed and all 183 digests matching.

Approving. Scope, so this isn't read as broader than it is: I reviewed the qdrant chart, its packaging, and its behaviour alongside sibling charts in an umbrella release — not the main merge beyond confirming it left this chart alone. The two notes above are knowingly left open.

@arunesh-j
arunesh-j merged commit 8e783f6 into main Aug 25, 2026
1 check passed
@arunesh-j
arunesh-j deleted the feat/qdrant branch August 25, 2026 12:27
arunesh-j added a commit that referenced this pull request Aug 28, 2026
docs/index.yaml conflicted again because both sides regenerated it wholesale -
main gained the qdrant chart (#310) and the README fixes (#319). Resolved the
same way: discard both versions and re-run

  helm repo index . --url https://helm.zop.dev

over the merged docs/ directory. Verified the only semantic change against main
is the added n8n v0.0.1 entry - 184 tarball digests match, nothing removed,
nothing altered, and qdrant v0.0.1 from main is intact.

Also adds n8n to the applications table in the top-level README. #319 landed
that table with a Deploy column while this branch was open, so without this the
chart would merge unlisted. Both of its links were checked live.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants