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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions charts/qdrant/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,26 @@ pdb:
The chart fails rendering when `cluster.enabled=true` is combined with one replica, disabled persistence, or a shared `persistence.existingClaim`.
When enabled, the StatefulSet starts ordinal 0 with a stable `--uri`; later pods join through `--bootstrap` and publish their own stable peer URI.

## Network Policy

`networkPolicy.enabled=true` restricts inbound HTTP and gRPC traffic to the configured `networkPolicy.ingressFrom` peers, or to all
namespaces when `ingressFrom` is empty. Cluster p2p ingress is allowed from Qdrant pods when `cluster.enabled=true`.

Set `networkPolicy.extraEgress` to enable egress isolation and append custom egress rules. The chart preserves DNS and broad HTTPS
egress to any IPv4/IPv6 destination, and also preserves Qdrant p2p egress between pods when distributed mode is enabled:

```yaml
networkPolicy:
enabled: true
extraEgress:
- to:
- ipBlock:
cidr: 10.0.0.0/8
ports:
- protocol: TCP
port: 6333
```

## Monitoring

Qdrant exposes Prometheus metrics on `/metrics`. Enable a ServiceMonitor when Prometheus Operator CRDs are installed:
Expand Down
111 changes: 72 additions & 39 deletions charts/qdrant/templates/NOTES.txt
Original file line number Diff line number Diff line change
@@ -1,63 +1,96 @@
{{/* SPDX-License-Identifier: Apache-2.0 */}}
Qdrant has been deployed.
1. Summary

Access:
Qdrant release: {{ .Release.Name }}
Namespace: {{ .Release.Namespace }}
HTTP API port: {{ .Values.service.port }}
gRPC API port: {{ .Values.service.grpcPort }}

Internal service:
- HTTP API: http://{{ include "qdrant.fullname" . }}.{{ .Release.Namespace }}.svc:{{ .Values.service.port }}
- gRPC API: {{ include "qdrant.fullname" . }}.{{ .Release.Namespace }}.svc:{{ .Values.service.grpcPort }}
2. Workload

Port-forward:
- kubectl port-forward svc/{{ include "qdrant.fullname" . }} 6333:{{ .Values.service.port }} -n {{ .Release.Namespace }}
- curl http://127.0.0.1:6333/readyz
Check the StatefulSet and rollout status:

kubectl get statefulset {{ include "qdrant.fullname" . }} -n {{ .Release.Namespace }}
kubectl rollout status statefulset/{{ include "qdrant.fullname" . }} -n {{ .Release.Namespace }}

3. Access

Internal endpoints:

http://{{ include "qdrant.fullname" . }}.{{ .Release.Namespace }}.svc:{{ .Values.service.port }}
{{ include "qdrant.fullname" . }}.{{ .Release.Namespace }}.svc:{{ .Values.service.grpcPort }}

Use a local port-forward when Ingress or Gateway API is not enabled:

kubectl port-forward svc/{{ include "qdrant.fullname" . }} 6333:{{ .Values.service.port }} -n {{ .Release.Namespace }}
curl http://127.0.0.1:6333/readyz

4. Credentials

{{- if or .Values.auth.apiKey .Values.auth.existingSecret }}
Authentication:
API key authentication is enabled.

Secret: {{ default (include "qdrant.fullname" .) .Values.auth.existingSecret }}
Key: {{ .Values.auth.apiKeyKey }}

API key authentication is enabled. Retrieve the key from:
- Secret: {{ default (include "qdrant.fullname" .) .Values.auth.existingSecret }}
- Key: {{ .Values.auth.apiKeyKey }}
Retrieve the API key:

Example:
- kubectl get secret {{ default (include "qdrant.fullname" .) .Values.auth.existingSecret }} -n {{ .Release.Namespace }} -o jsonpath='{.data.{{ .Values.auth.apiKeyKey }}}' | base64 -d
- curl -H "api-key: $QDRANT_API_KEY" http://127.0.0.1:6333/collections
kubectl get secret {{ default (include "qdrant.fullname" .) .Values.auth.existingSecret }} -n {{ .Release.Namespace }} -o jsonpath="{.data['{{ .Values.auth.apiKeyKey }}']}" | base64 -d
{{- else }}
API key authentication is disabled. Enable auth.apiKey or auth.existingSecret before exposing Qdrant to shared users.
{{- end }}

Storage:
5. Configuration

- Storage path: {{ .Values.persistence.mountPath }}
- Snapshot path: {{ .Values.snapshots.path }}
Storage path: {{ .Values.persistence.mountPath }}
Snapshot path: {{ .Values.snapshots.path }}
{{- if .Values.persistence.enabled }}
- Persistence: enabled
Persistence: enabled.
{{- else }}
- Persistence: disabled; data uses emptyDir and is lost when the pod is deleted
Persistence: disabled; data uses emptyDir and is lost when the pod is deleted.
{{- end }}

{{- if .Values.cluster.enabled }}
Distributed mode:

- Replicas: {{ .Values.replicaCount }}
- P2P service: {{ include "qdrant.fullname" . }}-headless.{{ .Release.Namespace }}.svc:{{ .Values.cluster.p2pPort }}
- Confirm cluster health with Qdrant collection and cluster APIs after bootstrap
Distributed mode: enabled with {{ .Values.replicaCount }} replicas.
P2P service: {{ include "qdrant.fullname" . }}-headless.{{ .Release.Namespace }}.svc:{{ .Values.cluster.p2pPort }}
{{- else }}
Distributed mode: disabled.
{{- end }}
{{- if .Values.networkPolicy.enabled }}
NetworkPolicy: enabled.
{{- else }}
NetworkPolicy: disabled.
{{- end }}

{{- if .Values.metrics.serviceMonitor.enabled }}
Monitoring:
6. Validation

- ServiceMonitor: {{ include "qdrant.fullname" . }}
- Metrics endpoint: /metrics on port http
{{- end }}
Run Helm tests:

helm test {{ .Release.Name }} -n {{ .Release.Namespace }}

Run the HelmForge chart gate from the ops repo:

make validate-chart CHART=qdrant

7. Troubleshooting

Inspect pods, logs, and PVCs:

kubectl get pod -l app.kubernetes.io/instance={{ .Release.Name }} -n {{ .Release.Namespace }}
kubectl logs statefulset/{{ include "qdrant.fullname" . }} -n {{ .Release.Namespace }}
kubectl describe pvc -l app.kubernetes.io/instance={{ .Release.Name }} -n {{ .Release.Namespace }}

8. Resources

Chart documentation:

https://helmforge.dev/docs/charts/qdrant

Troubleshooting:
Qdrant documentation:

- kubectl get pod -l app.kubernetes.io/instance={{ .Release.Name }} -n {{ .Release.Namespace }}
- kubectl logs statefulset/{{ include "qdrant.fullname" . }} -n {{ .Release.Namespace }}
- kubectl describe pvc -l app.kubernetes.io/instance={{ .Release.Name }} -n {{ .Release.Namespace }}
https://qdrant.tech/documentation/

Resources:
HelmForge issues:

- Chart documentation: https://helmforge.dev/docs/charts/qdrant
- Qdrant documentation: https://qdrant.tech/documentation/
- HelmForge issues: https://github.com/helmforgedev/charts/issues
https://github.com/helmforgedev/charts/issues

Happy Helmforging :)
27 changes: 27 additions & 0 deletions charts/qdrant/templates/_helpers.tpl
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,30 @@ app.kubernetes.io/part-of: helmforge
{{- end -}}
{{- define "qdrant.serviceAccountName" -}}{{- if .Values.serviceAccount.create -}}{{- default (include "qdrant.fullname" .) .Values.serviceAccount.name -}}{{- else -}}{{- default "default" .Values.serviceAccount.name -}}{{- end -}}{{- end -}}
{{- define "qdrant.image" -}}{{- printf "%s:%s" .Values.image.repository .Values.image.tag -}}{{- end -}}
{{- define "qdrant.validate" -}}
{{- if and .Values.cluster.enabled (lt (int .Values.replicaCount) 2) -}}
{{- fail "cluster.enabled requires replicaCount >= 2" -}}
{{- end -}}
{{- if and (gt (int .Values.replicaCount) 1) (not .Values.cluster.enabled) -}}
{{- fail "replicaCount > 1 requires cluster.enabled=true so Qdrant pods form one distributed cluster" -}}
{{- end -}}
{{- if and .Values.cluster.enabled (not .Values.persistence.enabled) -}}
{{- fail "cluster.enabled requires persistence.enabled=true" -}}
{{- end -}}
{{- if and .Values.cluster.enabled .Values.persistence.existingClaim -}}
{{- fail "cluster.enabled requires generated per-pod PVCs; do not set persistence.existingClaim" -}}
{{- end -}}
{{- if and .Values.cluster.enabled .Values.app.command -}}
{{- fail "cluster.enabled manages the Qdrant startup command for peer bootstrap; use app.args for extra Qdrant flags" -}}
{{- end -}}
{{- if and .Values.ingress.enabled (empty .Values.ingress.hosts) -}}
{{- fail "ingress.hosts must contain at least one host when ingress.enabled=true" -}}
{{- end -}}
{{- $podLabels := .Values.podLabels | default dict -}}
{{- if hasKey $podLabels "app.kubernetes.io/name" -}}
{{- fail "podLabels must not override the selector label app.kubernetes.io/name" -}}
{{- end -}}
{{- if hasKey $podLabels "app.kubernetes.io/instance" -}}
{{- fail "podLabels must not override the selector label app.kubernetes.io/instance" -}}
{{- end -}}
{{- end -}}
4 changes: 3 additions & 1 deletion charts/qdrant/templates/ingress.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,9 @@ metadata:
{{- toYaml . | nindent 4 }}
{{- end }}
spec:
ingressClassName: {{ .Values.ingress.ingressClassName | quote }}
{{- with .Values.ingress.ingressClassName }}
ingressClassName: {{ . | quote }}
{{- end }}
{{- with .Values.ingress.tls }}
tls:
{{- toYaml . | nindent 4 }}
Expand Down
32 changes: 32 additions & 0 deletions charts/qdrant/templates/networkpolicy.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ spec:
{{- include "qdrant.selectorLabels" . | nindent 6 }}
policyTypes:
- Ingress
{{- if .Values.networkPolicy.extraEgress }}
- Egress
{{- end }}
ingress:
- from:
{{- if .Values.networkPolicy.ingressFrom }}
Expand All @@ -33,4 +36,33 @@ spec:
- protocol: TCP
port: p2p
{{- end }}
{{- with .Values.networkPolicy.extraEgress }}
egress:
- to:
- namespaceSelector: {}
ports:
- protocol: UDP
port: 53
- protocol: TCP
port: 53
{{- /* Preserve broad HTTPS for compatibility with external Qdrant/cloud endpoints when egress isolation is enabled. */}}
- to:
- ipBlock:
cidr: 0.0.0.0/0
- ipBlock:
cidr: ::/0
ports:
- protocol: TCP
port: 443
{{- if $.Values.cluster.enabled }}
- to:
- podSelector:
matchLabels:
{{- include "qdrant.selectorLabels" $ | nindent 14 }}
ports:
- protocol: TCP
port: p2p
{{- end }}
{{- toYaml . | nindent 4 }}
{{- end }}
{{- end }}
2 changes: 1 addition & 1 deletion charts/qdrant/templates/statefulset.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ spec:
template:
metadata:
labels:
{{- include "qdrant.labels" . | nindent 8 }}
{{- include "qdrant.selectorLabels" . | nindent 8 }}
{{- with .Values.podLabels }}
{{- toYaml . | nindent 8 }}
{{- end }}
Expand Down
16 changes: 1 addition & 15 deletions charts/qdrant/templates/validate.yaml
Original file line number Diff line number Diff line change
@@ -1,16 +1,2 @@
{{/* SPDX-License-Identifier: Apache-2.0 */}}
{{- if and .Values.cluster.enabled (lt (int .Values.replicaCount) 2) -}}
{{- fail "cluster.enabled requires replicaCount >= 2" -}}
{{- end -}}
{{- if and (gt (int .Values.replicaCount) 1) (not .Values.cluster.enabled) -}}
{{- fail "replicaCount > 1 requires cluster.enabled=true so Qdrant pods form one distributed cluster" -}}
{{- end -}}
{{- if and .Values.cluster.enabled (not .Values.persistence.enabled) -}}
{{- fail "cluster.enabled requires persistence.enabled=true" -}}
{{- end -}}
{{- if and .Values.cluster.enabled .Values.persistence.existingClaim -}}
{{- fail "cluster.enabled requires generated per-pod PVCs; do not set persistence.existingClaim" -}}
{{- end -}}
{{- if and .Values.cluster.enabled .Values.app.command -}}
{{- fail "cluster.enabled manages the Qdrant startup command for peer bootstrap; use app.args for extra Qdrant flags" -}}
{{- end -}}
{{- include "qdrant.validate" . -}}
14 changes: 14 additions & 0 deletions charts/qdrant/tests/networkpolicy-extra-egress-values.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# SPDX-License-Identifier: Apache-2.0
networkPolicy:
enabled: true
ingressFrom:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: rag
extraEgress:
- to:
- ipBlock:
cidr: 10.0.0.0/8
ports:
- protocol: TCP
port: 6333
36 changes: 36 additions & 0 deletions charts/qdrant/tests/networkpolicy_test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@ tests:
content:
protocol: TCP
port: grpc
- notContains:
path: spec.policyTypes
content: Egress
- it: allows Qdrant p2p traffic when clustering is enabled
values:
- networkpolicy-values.yaml
Expand All @@ -36,3 +39,36 @@ tests:
content:
protocol: TCP
port: p2p
- it: enables egress isolation when extraEgress is configured
values:
- networkpolicy-extra-egress-values.yaml
asserts:
- contains:
path: spec.policyTypes
content: Egress
- equal:
path: spec.egress[0].ports[0].port
value: 53
- equal:
path: spec.egress[1].ports[0].port
value: 443
- equal:
path: spec.egress[2].ports[0].port
value: 6333
- it: preserves cluster p2p egress when egress is isolated
values:
- networkpolicy-extra-egress-values.yaml
- cluster-values.yaml
asserts:
- contains:
path: spec.egress[2].to
content:
podSelector:
matchLabels:
app.kubernetes.io/name: qdrant
app.kubernetes.io/instance: RELEASE-NAME
- contains:
path: spec.egress[2].ports
content:
protocol: TCP
port: p2p
3 changes: 3 additions & 0 deletions charts/qdrant/tests/podlabels-selector-instance-values.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# SPDX-License-Identifier: Apache-2.0
podLabels:
app.kubernetes.io/instance: custom
3 changes: 3 additions & 0 deletions charts/qdrant/tests/podlabels-selector-name-values.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# SPDX-License-Identifier: Apache-2.0
podLabels:
app.kubernetes.io/name: custom
18 changes: 18 additions & 0 deletions charts/qdrant/tests/validation_test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -32,3 +32,21 @@ tests:
asserts:
- failedTemplate:
errorMessage: "cluster.enabled manages the Qdrant startup command for peer bootstrap; use app.args for extra Qdrant flags"
- it: fails when ingress is enabled without hosts
set:
ingress.enabled: true
asserts:
- failedTemplate:
errorMessage: "ingress.hosts must contain at least one host when ingress.enabled=true"
- it: fails when podLabels override selector name
values:
- podlabels-selector-name-values.yaml
asserts:
- failedTemplate:
errorMessage: "podLabels must not override the selector label app.kubernetes.io/name"
- it: fails when podLabels override selector instance
values:
- podlabels-selector-instance-values.yaml
asserts:
- failedTemplate:
errorMessage: "podLabels must not override the selector label app.kubernetes.io/instance"
3 changes: 2 additions & 1 deletion charts/qdrant/values.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,8 @@
"type": "object",
"properties": {
"enabled": { "type": "boolean", "default": false },
"ingressFrom": { "type": "array", "items": { "type": "object" } }
"ingressFrom": { "type": "array", "items": { "type": "object" } },
"extraEgress": { "type": "array", "items": { "type": "object" } }
}
},
"probes": { "$ref": "#/definitions/probes" },
Expand Down
2 changes: 2 additions & 0 deletions charts/qdrant/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,8 @@ networkPolicy:
enabled: false
# -- Ingress peers allowed to reach Qdrant. Empty allows all namespaces when policy is enabled.
ingressFrom: []
# -- Additional egress rules. When set, the policy also isolates egress while preserving DNS, broad HTTPS to any IPv4/IPv6 destination, and cluster p2p traffic.
extraEgress: []

probes:
# -- Startup probe settings.
Expand Down