From 8586b1487aa70b8a6ecaee011a8aaa31b8505e4b Mon Sep 17 00:00:00 2001 From: Tamal Saha Date: Thu, 30 Apr 2026 11:09:53 +0600 Subject: [PATCH 01/10] docs(qdrant): add guides and examples Signed-off-by: Tamal Saha --- .../qdrant/autoscaler/compute/autoscaler.yaml | 22 +++ .../qdrant/autoscaler/storage/autoscaler.yaml | 19 +++ .../qdrant/quickstart/distributed.yaml | 16 +++ .../qdrant/reconfigure-tls/ops-request.yaml | 11 ++ .../qdrant/reconfigure/ops-request.yaml | 13 ++ docs/examples/qdrant/restart/ops-request.yaml | 9 ++ .../qdrant/rotate-auth/ops-request.yaml | 10 ++ .../horizontal-scaling/ops-request.yaml | 11 ++ .../scaling/vertical-scaling/ops-request.yaml | 18 +++ .../qdrant/update-version/ops-request.yaml | 11 ++ .../qdrant/volume-expansion/ops-request.yaml | 12 ++ docs/guides/qdrant/README.md | 99 ++++++++++++++ docs/guides/qdrant/_index.md | 10 ++ docs/guides/qdrant/autoscaler/_index.md | 10 ++ .../qdrant/autoscaler/compute/_index.md | 10 ++ .../qdrant/autoscaler/compute/cluster.md | 17 +++ .../qdrant/autoscaler/compute/overview.md | 53 ++++++++ docs/guides/qdrant/autoscaler/overview.md | 36 +++++ .../qdrant/autoscaler/storage/_index.md | 10 ++ .../qdrant/autoscaler/storage/cluster.md | 17 +++ .../qdrant/autoscaler/storage/overview.md | 54 ++++++++ docs/guides/qdrant/backup/_index.md | 10 ++ docs/guides/qdrant/backup/overview.md | 75 +++++++++++ docs/guides/qdrant/concepts/_index.md | 10 ++ docs/guides/qdrant/concepts/autoscaler.md | 55 ++++++++ docs/guides/qdrant/concepts/catalog.md | 47 +++++++ docs/guides/qdrant/concepts/opsrequest.md | 56 ++++++++ docs/guides/qdrant/concepts/qdrant.md | 51 +++++++ docs/guides/qdrant/configuration/_index.md | 10 ++ .../qdrant/configuration/using-config-file.md | 90 +++++++++++++ docs/guides/qdrant/custom-rbac/_index.md | 10 ++ .../qdrant/custom-rbac/using-custom-rbac.md | 127 ++++++++++++++++++ docs/guides/qdrant/monitoring/_index.md | 10 ++ docs/guides/qdrant/monitoring/overview.md | 39 ++++++ .../monitoring/using-builtin-prometheus.md | 73 ++++++++++ .../monitoring/using-prometheus-operator.md | 76 +++++++++++ docs/guides/qdrant/ops-request/_index.md | 10 ++ docs/guides/qdrant/ops-request/overview.md | 40 ++++++ docs/guides/qdrant/private-registry/_index.md | 10 ++ .../using-private-registry.md | 81 +++++++++++ docs/guides/qdrant/quickstart/_index.md | 10 ++ docs/guides/qdrant/quickstart/quickstart.md | 84 ++++++++++++ docs/guides/qdrant/quickstart/rbac.md | 60 +++++++++ docs/guides/qdrant/reconfigure-tls/_index.md | 10 ++ .../guides/qdrant/reconfigure-tls/overview.md | 55 ++++++++ .../qdrant/reconfigure-tls/reconfigure-tls.md | 63 +++++++++ docs/guides/qdrant/reconfigure/_index.md | 10 ++ docs/guides/qdrant/reconfigure/overview.md | 54 ++++++++ docs/guides/qdrant/reconfigure/reconfigure.md | 62 +++++++++ docs/guides/qdrant/restart/_index.md | 10 ++ docs/guides/qdrant/restart/restart.md | 55 ++++++++ docs/guides/qdrant/rotate-auth/_index.md | 10 ++ docs/guides/qdrant/rotate-auth/overview.md | 54 ++++++++ docs/guides/qdrant/rotate-auth/rotateauth.md | 59 ++++++++ docs/guides/qdrant/scaling/_index.md | 10 ++ .../scaling/horizontal-scaling/_index.md | 10 ++ .../scaling/horizontal-scaling/overview.md | 54 ++++++++ .../scale-horizontally/index.md | 56 ++++++++ .../qdrant/scaling/vertical-scaling/_index.md | 10 ++ .../scaling/vertical-scaling/overview.md | 54 ++++++++ .../scale-vertically/index.md | 57 ++++++++ docs/guides/qdrant/tls/_index.md | 10 ++ docs/guides/qdrant/tls/configure/index.md | 70 ++++++++++ docs/guides/qdrant/tls/overview.md | 39 ++++++ docs/guides/qdrant/update-version/_index.md | 10 ++ docs/guides/qdrant/update-version/overview.md | 55 ++++++++ .../update-version/versionupgrading/index.md | 54 ++++++++ docs/guides/qdrant/volume-expansion/_index.md | 10 ++ .../qdrant/volume-expansion/overview.md | 55 ++++++++ .../volume-expansion/volume-expansion.md | 55 ++++++++ 70 files changed, 2553 insertions(+) create mode 100644 docs/examples/qdrant/autoscaler/compute/autoscaler.yaml create mode 100644 docs/examples/qdrant/autoscaler/storage/autoscaler.yaml create mode 100644 docs/examples/qdrant/quickstart/distributed.yaml create mode 100644 docs/examples/qdrant/reconfigure-tls/ops-request.yaml create mode 100644 docs/examples/qdrant/reconfigure/ops-request.yaml create mode 100644 docs/examples/qdrant/restart/ops-request.yaml create mode 100644 docs/examples/qdrant/rotate-auth/ops-request.yaml create mode 100644 docs/examples/qdrant/scaling/horizontal-scaling/ops-request.yaml create mode 100644 docs/examples/qdrant/scaling/vertical-scaling/ops-request.yaml create mode 100644 docs/examples/qdrant/update-version/ops-request.yaml create mode 100644 docs/examples/qdrant/volume-expansion/ops-request.yaml create mode 100644 docs/guides/qdrant/README.md create mode 100644 docs/guides/qdrant/_index.md create mode 100644 docs/guides/qdrant/autoscaler/_index.md create mode 100644 docs/guides/qdrant/autoscaler/compute/_index.md create mode 100644 docs/guides/qdrant/autoscaler/compute/cluster.md create mode 100644 docs/guides/qdrant/autoscaler/compute/overview.md create mode 100644 docs/guides/qdrant/autoscaler/overview.md create mode 100644 docs/guides/qdrant/autoscaler/storage/_index.md create mode 100644 docs/guides/qdrant/autoscaler/storage/cluster.md create mode 100644 docs/guides/qdrant/autoscaler/storage/overview.md create mode 100644 docs/guides/qdrant/backup/_index.md create mode 100644 docs/guides/qdrant/backup/overview.md create mode 100644 docs/guides/qdrant/concepts/_index.md create mode 100644 docs/guides/qdrant/concepts/autoscaler.md create mode 100644 docs/guides/qdrant/concepts/catalog.md create mode 100644 docs/guides/qdrant/concepts/opsrequest.md create mode 100644 docs/guides/qdrant/concepts/qdrant.md create mode 100644 docs/guides/qdrant/configuration/_index.md create mode 100644 docs/guides/qdrant/configuration/using-config-file.md create mode 100644 docs/guides/qdrant/custom-rbac/_index.md create mode 100644 docs/guides/qdrant/custom-rbac/using-custom-rbac.md create mode 100644 docs/guides/qdrant/monitoring/_index.md create mode 100644 docs/guides/qdrant/monitoring/overview.md create mode 100644 docs/guides/qdrant/monitoring/using-builtin-prometheus.md create mode 100644 docs/guides/qdrant/monitoring/using-prometheus-operator.md create mode 100644 docs/guides/qdrant/ops-request/_index.md create mode 100644 docs/guides/qdrant/ops-request/overview.md create mode 100644 docs/guides/qdrant/private-registry/_index.md create mode 100644 docs/guides/qdrant/private-registry/using-private-registry.md create mode 100644 docs/guides/qdrant/quickstart/_index.md create mode 100644 docs/guides/qdrant/quickstart/quickstart.md create mode 100644 docs/guides/qdrant/quickstart/rbac.md create mode 100644 docs/guides/qdrant/reconfigure-tls/_index.md create mode 100644 docs/guides/qdrant/reconfigure-tls/overview.md create mode 100644 docs/guides/qdrant/reconfigure-tls/reconfigure-tls.md create mode 100644 docs/guides/qdrant/reconfigure/_index.md create mode 100644 docs/guides/qdrant/reconfigure/overview.md create mode 100644 docs/guides/qdrant/reconfigure/reconfigure.md create mode 100644 docs/guides/qdrant/restart/_index.md create mode 100644 docs/guides/qdrant/restart/restart.md create mode 100644 docs/guides/qdrant/rotate-auth/_index.md create mode 100644 docs/guides/qdrant/rotate-auth/overview.md create mode 100644 docs/guides/qdrant/rotate-auth/rotateauth.md create mode 100644 docs/guides/qdrant/scaling/_index.md create mode 100644 docs/guides/qdrant/scaling/horizontal-scaling/_index.md create mode 100644 docs/guides/qdrant/scaling/horizontal-scaling/overview.md create mode 100644 docs/guides/qdrant/scaling/horizontal-scaling/scale-horizontally/index.md create mode 100644 docs/guides/qdrant/scaling/vertical-scaling/_index.md create mode 100644 docs/guides/qdrant/scaling/vertical-scaling/overview.md create mode 100644 docs/guides/qdrant/scaling/vertical-scaling/scale-vertically/index.md create mode 100644 docs/guides/qdrant/tls/_index.md create mode 100644 docs/guides/qdrant/tls/configure/index.md create mode 100644 docs/guides/qdrant/tls/overview.md create mode 100644 docs/guides/qdrant/update-version/_index.md create mode 100644 docs/guides/qdrant/update-version/overview.md create mode 100644 docs/guides/qdrant/update-version/versionupgrading/index.md create mode 100644 docs/guides/qdrant/volume-expansion/_index.md create mode 100644 docs/guides/qdrant/volume-expansion/overview.md create mode 100644 docs/guides/qdrant/volume-expansion/volume-expansion.md diff --git a/docs/examples/qdrant/autoscaler/compute/autoscaler.yaml b/docs/examples/qdrant/autoscaler/compute/autoscaler.yaml new file mode 100644 index 000000000..a28811cfd --- /dev/null +++ b/docs/examples/qdrant/autoscaler/compute/autoscaler.yaml @@ -0,0 +1,22 @@ +apiVersion: autoscaling.kubedb.com/v1alpha1 +kind: QdrantAutoscaler +metadata: + name: qdrant-as-compute + namespace: demo +spec: + databaseRef: + name: qdrant-sample + opsRequestOptions: + timeout: 3m + apply: IfReady + compute: + node: + trigger: "On" + minAllowed: + cpu: 250m + memory: 512Mi + maxAllowed: + cpu: "2" + memory: 4Gi + controlledResources: ["cpu", "memory"] + containerControlledValues: RequestsAndLimits \ No newline at end of file diff --git a/docs/examples/qdrant/autoscaler/storage/autoscaler.yaml b/docs/examples/qdrant/autoscaler/storage/autoscaler.yaml new file mode 100644 index 000000000..928672895 --- /dev/null +++ b/docs/examples/qdrant/autoscaler/storage/autoscaler.yaml @@ -0,0 +1,19 @@ +apiVersion: autoscaling.kubedb.com/v1alpha1 +kind: QdrantAutoscaler +metadata: + name: qdrant-as-storage + namespace: demo +spec: + databaseRef: + name: qdrant-sample + opsRequestOptions: + timeout: 3m + apply: IfReady + storage: + node: + trigger: "On" + expansionMode: Online + usageThreshold: 70 + scalingThreshold: 50 + minAllowed: 2Gi + maxAllowed: 50Gi \ No newline at end of file diff --git a/docs/examples/qdrant/quickstart/distributed.yaml b/docs/examples/qdrant/quickstart/distributed.yaml new file mode 100644 index 000000000..9afa2f981 --- /dev/null +++ b/docs/examples/qdrant/quickstart/distributed.yaml @@ -0,0 +1,16 @@ +apiVersion: kubedb.com/v1alpha2 +kind: Qdrant +metadata: + name: qdrant-sample + namespace: demo +spec: + version: 1.17.0 + mode: Distributed + replicas: 3 + storage: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 2Gi + deletionPolicy: WipeOut diff --git a/docs/examples/qdrant/reconfigure-tls/ops-request.yaml b/docs/examples/qdrant/reconfigure-tls/ops-request.yaml new file mode 100644 index 000000000..825d29ef7 --- /dev/null +++ b/docs/examples/qdrant/reconfigure-tls/ops-request.yaml @@ -0,0 +1,11 @@ +apiVersion: ops.kubedb.com/v1alpha1 +kind: QdrantOpsRequest +metadata: + name: qdrant-reconfigure-tls + namespace: demo +spec: + type: ReconfigureTLS + databaseRef: + name: qdrant-sample + tls: + rotateCertificates: true \ No newline at end of file diff --git a/docs/examples/qdrant/reconfigure/ops-request.yaml b/docs/examples/qdrant/reconfigure/ops-request.yaml new file mode 100644 index 000000000..bba0604b3 --- /dev/null +++ b/docs/examples/qdrant/reconfigure/ops-request.yaml @@ -0,0 +1,13 @@ +apiVersion: ops.kubedb.com/v1alpha1 +kind: QdrantOpsRequest +metadata: + name: qdrant-reconfigure + namespace: demo +spec: + type: Reconfigure + databaseRef: + name: qdrant-sample + configuration: + applyConfig: + production.yaml: | + log_level: INFO \ No newline at end of file diff --git a/docs/examples/qdrant/restart/ops-request.yaml b/docs/examples/qdrant/restart/ops-request.yaml new file mode 100644 index 000000000..7fd92ce28 --- /dev/null +++ b/docs/examples/qdrant/restart/ops-request.yaml @@ -0,0 +1,9 @@ +apiVersion: ops.kubedb.com/v1alpha1 +kind: QdrantOpsRequest +metadata: + name: qdrant-restart + namespace: demo +spec: + type: Restart + databaseRef: + name: qdrant-sample \ No newline at end of file diff --git a/docs/examples/qdrant/rotate-auth/ops-request.yaml b/docs/examples/qdrant/rotate-auth/ops-request.yaml new file mode 100644 index 000000000..61d192a3d --- /dev/null +++ b/docs/examples/qdrant/rotate-auth/ops-request.yaml @@ -0,0 +1,10 @@ +apiVersion: ops.kubedb.com/v1alpha1 +kind: QdrantOpsRequest +metadata: + name: qdrant-rotate-auth + namespace: demo +spec: + type: RotateAuth + databaseRef: + name: qdrant-sample + timeout: 5m \ No newline at end of file diff --git a/docs/examples/qdrant/scaling/horizontal-scaling/ops-request.yaml b/docs/examples/qdrant/scaling/horizontal-scaling/ops-request.yaml new file mode 100644 index 000000000..6f1b3fae7 --- /dev/null +++ b/docs/examples/qdrant/scaling/horizontal-scaling/ops-request.yaml @@ -0,0 +1,11 @@ +apiVersion: ops.kubedb.com/v1alpha1 +kind: QdrantOpsRequest +metadata: + name: qdrant-horizontal-scale + namespace: demo +spec: + type: HorizontalScaling + databaseRef: + name: qdrant-sample + horizontalScaling: + node: 5 \ No newline at end of file diff --git a/docs/examples/qdrant/scaling/vertical-scaling/ops-request.yaml b/docs/examples/qdrant/scaling/vertical-scaling/ops-request.yaml new file mode 100644 index 000000000..146b71415 --- /dev/null +++ b/docs/examples/qdrant/scaling/vertical-scaling/ops-request.yaml @@ -0,0 +1,18 @@ +apiVersion: ops.kubedb.com/v1alpha1 +kind: QdrantOpsRequest +metadata: + name: qdrant-vertical-scale + namespace: demo +spec: + type: VerticalScaling + databaseRef: + name: qdrant-sample + verticalScaling: + node: + resources: + requests: + cpu: "500m" + memory: 1Gi + limits: + cpu: "1" + memory: 2Gi \ No newline at end of file diff --git a/docs/examples/qdrant/update-version/ops-request.yaml b/docs/examples/qdrant/update-version/ops-request.yaml new file mode 100644 index 000000000..25f4fc93e --- /dev/null +++ b/docs/examples/qdrant/update-version/ops-request.yaml @@ -0,0 +1,11 @@ +apiVersion: ops.kubedb.com/v1alpha1 +kind: QdrantOpsRequest +metadata: + name: qdrant-update-version + namespace: demo +spec: + type: UpdateVersion + databaseRef: + name: qdrant-sample + updateVersion: + targetVersion: 1.18.0 \ No newline at end of file diff --git a/docs/examples/qdrant/volume-expansion/ops-request.yaml b/docs/examples/qdrant/volume-expansion/ops-request.yaml new file mode 100644 index 000000000..f2f022df7 --- /dev/null +++ b/docs/examples/qdrant/volume-expansion/ops-request.yaml @@ -0,0 +1,12 @@ +apiVersion: ops.kubedb.com/v1alpha1 +kind: QdrantOpsRequest +metadata: + name: qdrant-volume-expand + namespace: demo +spec: + type: VolumeExpansion + databaseRef: + name: qdrant-sample + volumeExpansion: + mode: Online + node: 4Gi \ No newline at end of file diff --git a/docs/guides/qdrant/README.md b/docs/guides/qdrant/README.md new file mode 100644 index 000000000..dc5d8a50f --- /dev/null +++ b/docs/guides/qdrant/README.md @@ -0,0 +1,99 @@ +--- +title: Qdrant +menu: + docs_{{ .version }}: + identifier: qdrant-readme + name: Qdrant + parent: qdrant-guides + weight: 10 +menu_name: docs_{{ .version }} +section_menu_id: guides +url: /docs/{{ .version }}/guides/qdrant/ +aliases: + - /docs/{{ .version }}/guides/qdrant/README/ +--- + +> New to KubeDB? Please start [here](/docs/README.md). + +# Overview + +KubeDB supports Qdrant vector databases through the `Qdrant` CRD. + +## Supported Qdrant Features + +| Features | Availability | +|-------------------------------------|:------------:| +| Standalone provisioning | ✓ | +| Distributed provisioning | ✓ | +| TLS | ✓ | +| Backup (logical and volume snapshot)| ✓ | +| Monitoring | ✓ | +| Ops Requests | ✓ | +| Autoscaler | No | + +## Supported Ops Requests + +- HorizontalScaling +- Reconfigure +- ReconfigureTLS +- Restart +- RotateAuth +- VerticalScaling +- VolumeExpansion +- UpdateVersion + +## Example Qdrant Manifest + +```yaml +apiVersion: kubedb.com/v1alpha2 +kind: Qdrant +metadata: + name: qdrant-sample +spec: + version: 1.17.0 + mode: Distributed + replicas: 3 + storage: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 2Gi + deletionPolicy: WipeOut +``` + +## User Guide + +- [Quickstart Qdrant](/docs/guides/qdrant/quickstart/quickstart.md) with KubeDB operator. +- [Qdrant CRD Concept](/docs/guides/qdrant/concepts/qdrant.md). +- [QdrantVersion CRD Concept](/docs/guides/qdrant/concepts/catalog.md). +- [QdrantOpsRequest CRD Concept](/docs/guides/qdrant/concepts/opsrequest.md). +- [QdrantAutoscaler CRD Concept](/docs/guides/qdrant/concepts/autoscaler.md). +- [Monitoring](/docs/guides/qdrant/monitoring/overview.md) for metrics collection guidance. +- [TLS](/docs/guides/qdrant/tls/overview.md) for client and p2p security guidance. +- [Backup](/docs/guides/qdrant/backup/overview.md) for logical and snapshot backup approaches. +- [Ops Request](/docs/guides/qdrant/ops-request/overview.md) for supported operational changes. +- [Autoscaler](/docs/guides/qdrant/autoscaler/overview.md) for current documentation status. +- [Private Registry](/docs/guides/qdrant/private-registry/using-private-registry.md) +- [Custom RBAC](/docs/guides/qdrant/custom-rbac/using-custom-rbac.md) +- [Custom Configuration](/docs/guides/qdrant/configuration/using-config-file.md) +- [Builtin Prometheus Monitoring](/docs/guides/qdrant/monitoring/using-builtin-prometheus.md) +- [Prometheus Operator Monitoring](/docs/guides/qdrant/monitoring/using-prometheus-operator.md) +- [Configure TLS](/docs/guides/qdrant/tls/configure/) +- [Reconfigure Details](/docs/guides/qdrant/reconfigure/reconfigure.md) +- [Reconfigure TLS Details](/docs/guides/qdrant/reconfigure-tls/reconfigure-tls.md) +- [Rotate Auth Details](/docs/guides/qdrant/rotate-auth/rotateauth.md) +- [Horizontal Scaling Details](/docs/guides/qdrant/scaling/horizontal-scaling/scale-horizontally/) +- [Vertical Scaling Details](/docs/guides/qdrant/scaling/vertical-scaling/scale-vertically/) +- [Update Version Details](/docs/guides/qdrant/update-version/versionupgrading/) +- [Volume Expansion Details](/docs/guides/qdrant/volume-expansion/volume-expansion.md) +- [Reconfigure](/docs/guides/qdrant/reconfigure/overview.md) +- [Reconfigure TLS](/docs/guides/qdrant/reconfigure-tls/overview.md) +- [Restart](/docs/guides/qdrant/restart/restart.md) +- [Rotate Auth](/docs/guides/qdrant/rotate-auth/overview.md) +- [Update Version](/docs/guides/qdrant/update-version/overview.md) +- [Volume Expansion](/docs/guides/qdrant/volume-expansion/overview.md) +- [Horizontal Scaling](/docs/guides/qdrant/scaling/horizontal-scaling/overview.md) +- [Vertical Scaling](/docs/guides/qdrant/scaling/vertical-scaling/overview.md) +- [Compute Autoscaler](/docs/guides/qdrant/autoscaler/compute/overview.md) +- [Storage Autoscaler](/docs/guides/qdrant/autoscaler/storage/overview.md) \ No newline at end of file diff --git a/docs/guides/qdrant/_index.md b/docs/guides/qdrant/_index.md new file mode 100644 index 000000000..b77c4abea --- /dev/null +++ b/docs/guides/qdrant/_index.md @@ -0,0 +1,10 @@ +--- +title: Qdrant +menu: + docs_{{ .version }}: + identifier: qdrant-guides + name: Qdrant + parent: guides + weight: 17 +menu_name: docs_{{ .version }} +--- \ No newline at end of file diff --git a/docs/guides/qdrant/autoscaler/_index.md b/docs/guides/qdrant/autoscaler/_index.md new file mode 100644 index 000000000..2bc9ca6aa --- /dev/null +++ b/docs/guides/qdrant/autoscaler/_index.md @@ -0,0 +1,10 @@ +--- +title: Qdrant Autoscaler +menu: + docs_{{ .version }}: + identifier: qdrant-autoscaler + name: Autoscaler + parent: qdrant-guides + weight: 40 +menu_name: docs_{{ .version }} +--- diff --git a/docs/guides/qdrant/autoscaler/compute/_index.md b/docs/guides/qdrant/autoscaler/compute/_index.md new file mode 100644 index 000000000..5160e8257 --- /dev/null +++ b/docs/guides/qdrant/autoscaler/compute/_index.md @@ -0,0 +1,10 @@ +--- +title: Compute +menu: + docs_{{ .version }}: + identifier: qdrant-autoscaler-compute + name: Compute + parent: qdrant-autoscaler + weight: 10 +menu_name: docs_{{ .version }} +--- diff --git a/docs/guides/qdrant/autoscaler/compute/cluster.md b/docs/guides/qdrant/autoscaler/compute/cluster.md new file mode 100644 index 000000000..f37c7a9b7 --- /dev/null +++ b/docs/guides/qdrant/autoscaler/compute/cluster.md @@ -0,0 +1,17 @@ +--- +title: Qdrant Compute Autoscaler Cluster +menu: + docs_{{ .version }}: + identifier: qdrant-autoscaler-compute-cluster + name: Cluster + parent: qdrant-autoscaler-compute + weight: 20 +menu_name: docs_{{ .version }} +section_menu_id: guides +--- + +# Qdrant Compute Autoscaler + +This repository does not currently contain a `QdrantAutoscaler` Go type or CRD. + +Because of that, there is no repository-backed compute autoscaler manifest to publish for Qdrant at this time. \ No newline at end of file diff --git a/docs/guides/qdrant/autoscaler/compute/overview.md b/docs/guides/qdrant/autoscaler/compute/overview.md new file mode 100644 index 000000000..16ce05eef --- /dev/null +++ b/docs/guides/qdrant/autoscaler/compute/overview.md @@ -0,0 +1,53 @@ +--- +title: Qdrant Compute Autoscaler Overview +menu: + docs_{{ .version }}: + identifier: qdrant-autoscaler-compute-overview + name: Overview + parent: qdrant-autoscaler-compute + weight: 10 +menu_name: docs_{{ .version }} +section_menu_id: guides +--- + +# Qdrant Compute Autoscaler + +This guide shows how to configure compute autoscaling for Qdrant. + +## Before You Begin + +- Install KubeDB Autoscaler operator. +- Install metrics-server in your cluster. +- Use the example files from `docs/examples/qdrant/quickstart/distributed.yaml` and `docs/examples/qdrant/autoscaler/compute/autoscaler.yaml`. + +```bash +kubectl create ns demo +``` + +## Deploy Qdrant + +```bash +kubectl apply -f https://github.com/kubedb/docs/raw/{{< param "info.version" >}}/docs/examples/qdrant/quickstart/distributed.yaml +kubectl get qdrant -n demo qdrant-sample -w +``` + +## Apply QdrantAutoscaler + +```bash +kubectl apply -f https://github.com/kubedb/docs/raw/{{< param "info.version" >}}/docs/examples/qdrant/autoscaler/compute/autoscaler.yaml +``` + +## Verify + +```bash +kubectl get qdrantautoscaler -n demo qdrant-as-compute +kubectl describe qdrantautoscaler -n demo qdrant-as-compute +``` + +## Cleaning up + +```bash +kubectl delete qdrantautoscaler -n demo qdrant-as-compute +kubectl delete qdrant -n demo qdrant-sample +kubectl delete ns demo +``` diff --git a/docs/guides/qdrant/autoscaler/overview.md b/docs/guides/qdrant/autoscaler/overview.md new file mode 100644 index 000000000..385a6621b --- /dev/null +++ b/docs/guides/qdrant/autoscaler/overview.md @@ -0,0 +1,36 @@ +--- +title: Qdrant Autoscaler Overview +menu: + docs_{{ .version }}: + identifier: qdrant-autoscaler-overview + name: Overview + parent: qdrant-autoscaler + weight: 10 +menu_name: docs_{{ .version }} +section_menu_id: guides +--- + +# Qdrant Autoscaler + +This guide summarizes the autoscaling documentation status for Qdrant. + +## Before You Begin + +- Deploy Qdrant first using the [quickstart guide](/docs/guides/qdrant/quickstart/quickstart.md). +- Verify feature availability in your installed KubeDB release before applying any autoscaler examples. + +## Available Autoscaling Modes + +This repository does not currently contain a `QdrantAutoscaler` Go type or CRD. + +The compute and storage autoscaler pages are retained as placeholders so the guide tree is complete, but they do not represent CRD-validated manifests in the current repo. + +## Related Guides + +- [Compute Autoscaler](/docs/guides/qdrant/autoscaler/compute/overview.md) +- [Storage Autoscaler](/docs/guides/qdrant/autoscaler/storage/overview.md) + +## Next Steps + +- Start with conservative limits and thresholds. +- Review autoscaler recommendations before enabling broad production rollouts. diff --git a/docs/guides/qdrant/autoscaler/storage/_index.md b/docs/guides/qdrant/autoscaler/storage/_index.md new file mode 100644 index 000000000..0eb112cc1 --- /dev/null +++ b/docs/guides/qdrant/autoscaler/storage/_index.md @@ -0,0 +1,10 @@ +--- +title: Storage +menu: + docs_{{ .version }}: + identifier: qdrant-autoscaler-storage + name: Storage + parent: qdrant-autoscaler + weight: 20 +menu_name: docs_{{ .version }} +--- diff --git a/docs/guides/qdrant/autoscaler/storage/cluster.md b/docs/guides/qdrant/autoscaler/storage/cluster.md new file mode 100644 index 000000000..a90f8add1 --- /dev/null +++ b/docs/guides/qdrant/autoscaler/storage/cluster.md @@ -0,0 +1,17 @@ +--- +title: Qdrant Storage Autoscaler Cluster +menu: + docs_{{ .version }}: + identifier: qdrant-autoscaler-storage-cluster + name: Cluster + parent: qdrant-autoscaler-storage + weight: 20 +menu_name: docs_{{ .version }} +section_menu_id: guides +--- + +# Qdrant Storage Autoscaler + +This repository does not currently contain a `QdrantAutoscaler` Go type or CRD. + +Because of that, there is no repository-backed storage autoscaler manifest to publish for Qdrant at this time. \ No newline at end of file diff --git a/docs/guides/qdrant/autoscaler/storage/overview.md b/docs/guides/qdrant/autoscaler/storage/overview.md new file mode 100644 index 000000000..7d0dd5155 --- /dev/null +++ b/docs/guides/qdrant/autoscaler/storage/overview.md @@ -0,0 +1,54 @@ +--- +title: Qdrant Storage Autoscaler Overview +menu: + docs_{{ .version }}: + identifier: qdrant-autoscaler-storage-overview + name: Overview + parent: qdrant-autoscaler-storage + weight: 10 +menu_name: docs_{{ .version }} +section_menu_id: guides +--- + +# Qdrant Storage Autoscaler + +This guide shows how to configure storage autoscaling for Qdrant. + +## Before You Begin + +- StorageClass with `allowVolumeExpansion: true`. +- KubeDB Autoscaler operator installed. +- Use the example files from `docs/examples/qdrant/quickstart/distributed.yaml` and `docs/examples/qdrant/autoscaler/storage/autoscaler.yaml`. + +```bash +kubectl create ns demo +kubectl get storageclass +``` + +## Deploy Qdrant + +```bash +kubectl apply -f https://github.com/kubedb/docs/raw/{{< param "info.version" >}}/docs/examples/qdrant/quickstart/distributed.yaml +kubectl get qdrant -n demo qdrant-sample -w +``` + +## Apply QdrantAutoscaler + +```bash +kubectl apply -f https://github.com/kubedb/docs/raw/{{< param "info.version" >}}/docs/examples/qdrant/autoscaler/storage/autoscaler.yaml +``` + +## Verify + +```bash +kubectl get qdrantautoscaler -n demo qdrant-as-storage +kubectl describe qdrantautoscaler -n demo qdrant-as-storage +``` + +## Cleaning up + +```bash +kubectl delete qdrantautoscaler -n demo qdrant-as-storage +kubectl delete qdrant -n demo qdrant-sample +kubectl delete ns demo +``` diff --git a/docs/guides/qdrant/backup/_index.md b/docs/guides/qdrant/backup/_index.md new file mode 100644 index 000000000..05c7cb9cb --- /dev/null +++ b/docs/guides/qdrant/backup/_index.md @@ -0,0 +1,10 @@ +--- +title: Qdrant Backup +menu: + docs_{{ .version }}: + identifier: qdrant-backup + name: Backup + parent: qdrant-guides + weight: 30 +menu_name: docs_{{ .version }} +--- diff --git a/docs/guides/qdrant/backup/overview.md b/docs/guides/qdrant/backup/overview.md new file mode 100644 index 000000000..fc40109b0 --- /dev/null +++ b/docs/guides/qdrant/backup/overview.md @@ -0,0 +1,75 @@ +--- +title: Qdrant Backup Overview +menu: + docs_{{ .version }}: + identifier: qdrant-backup-overview + name: Overview + parent: qdrant-backup + weight: 10 +menu_name: docs_{{ .version }} +section_menu_id: guides +--- + +# Qdrant Backup + +This guide summarizes backup approaches for Qdrant and follows the same step-by-step flow as the operations guides. + +## Before You Begin + +- Install KubeDB, KubeStash, and a CSI snapshot-capable storage plugin if you plan to use volume snapshots. +- Deploy a Qdrant database in namespace `demo`. +- A single example file is not included under `docs/examples/qdrant/backup` because the exact `BackupConfiguration`, storage backend, and snapshot resources depend on your environment. + +```bash +kubectl create ns demo +``` + +## Deploy Qdrant + +```bash +kubectl apply -f https://github.com/kubedb/docs/raw/{{< param "info.version" >}}/docs/examples/qdrant/quickstart/distributed.yaml +kubectl get qdrant -n demo qdrant-sample -w +``` + +## Apply Backup Resources + +## Logical Backup (Restic Plugin) + +Use KubeStash with restic backend for object-level backup workflows. + +Typical resources: + +- `BackupStorage` +- `RetentionPolicy` +- `BackupConfiguration` +- `RestoreSession` + +Create backend-specific backup resources that point to your Qdrant database and storage backend. + +## Volume Snapshot Backup + +Use CSI snapshot compatible storage with snapshot classes for PVC level backup and restore. + +Typical resources: + +- `VolumeSnapshotClass` +- `BackupConfiguration` +- `RestoreSession` + +Use a snapshot-capable `StorageClass` and matching `VolumeSnapshotClass` for the PVCs created by your Qdrant deployment. + +## Verify + +```bash +kubectl get backupconfiguration -n demo +kubectl get restoresession -n demo +``` + +## Cleaning up + +```bash +kubectl delete backupconfiguration -n demo --all +kubectl delete restoresession -n demo --all +kubectl delete qdrant -n demo qdrant-sample +kubectl delete ns demo +``` diff --git a/docs/guides/qdrant/concepts/_index.md b/docs/guides/qdrant/concepts/_index.md new file mode 100644 index 000000000..b8aa8fa9e --- /dev/null +++ b/docs/guides/qdrant/concepts/_index.md @@ -0,0 +1,10 @@ +--- +title: Qdrant Concepts +menu: + docs_{{ .version }}: + identifier: qdrant-concepts + name: Concepts + parent: qdrant-guides + weight: 15 +menu_name: docs_{{ .version }} +--- \ No newline at end of file diff --git a/docs/guides/qdrant/concepts/autoscaler.md b/docs/guides/qdrant/concepts/autoscaler.md new file mode 100644 index 000000000..8923f31a4 --- /dev/null +++ b/docs/guides/qdrant/concepts/autoscaler.md @@ -0,0 +1,55 @@ +--- +title: QdrantAutoscaler CRD +menu: + docs_{{ .version }}: + identifier: qdrant-autoscaler-concepts + name: QdrantAutoscaler + parent: qdrant-concepts-qdrant + weight: 30 +menu_name: docs_{{ .version }} +section_menu_id: guides +--- + +> New to KubeDB? Please start [here](/docs/README.md). + +# QdrantAutoscaler + +## What is QdrantAutoscaler + +`QdrantAutoscaler` is documented here as a planned resource, but this repository does not currently contain the matching Go type or CRD. + +As a result, the sample shown below is illustrative only and should not be treated as a repository-backed manifest for the current release. + +## Sample QdrantAutoscaler + +```yaml +apiVersion: autoscaling.kubedb.com/v1alpha1 +kind: QdrantAutoscaler +metadata: + name: qdrant-as-compute + namespace: demo +spec: + databaseRef: + name: qdrant-sample + compute: + node: + trigger: "On" + minAllowed: + cpu: 250m + memory: 512Mi + maxAllowed: + cpu: "2" + memory: 4Gi +``` + +## Key fields + +- `spec.databaseRef.name` points to the target `Qdrant` database. +- `spec.compute` controls CPU and memory autoscaling behavior. +- `spec.storage` controls volume expansion thresholds and bounds. +- `spec.opsRequestOptions` configures generated ops request behavior. + +## Next Steps + +- Read [Qdrant autoscaler overview](/docs/guides/qdrant/autoscaler/overview.md). +- See [compute autoscaler guide](/docs/guides/qdrant/autoscaler/compute/overview.md) and [storage autoscaler guide](/docs/guides/qdrant/autoscaler/storage/overview.md). \ No newline at end of file diff --git a/docs/guides/qdrant/concepts/catalog.md b/docs/guides/qdrant/concepts/catalog.md new file mode 100644 index 000000000..49318243f --- /dev/null +++ b/docs/guides/qdrant/concepts/catalog.md @@ -0,0 +1,47 @@ +--- +title: QdrantVersion CRD +menu: + docs_{{ .version }}: + identifier: qdrant-catalog-concepts + name: QdrantVersion + parent: qdrant-concepts-qdrant + weight: 15 +menu_name: docs_{{ .version }} +section_menu_id: guides +--- + +> New to KubeDB? Please start [here](/docs/README.md). + +# QdrantVersion + +## What is QdrantVersion + +`QdrantVersion` is the catalog CRD that defines image and release metadata for KubeDB-managed Qdrant clusters. + +KubeDB resolves `Qdrant.spec.version` using this catalog entry. + +## QdrantVersion Specification + +```yaml +apiVersion: catalog.kubedb.com/v1alpha1 +kind: QdrantVersion +metadata: + name: "1.17.0" +spec: + version: "1.17.0" + db: + image: "kubedb/qdrant:1.17.0" + deprecated: false +``` + +## Key fields + +- `metadata.name` is referenced by `Qdrant.spec.version`. +- `spec.version` identifies the engine release. +- `spec.db.image` is the image used by Qdrant pods. +- `spec.deprecated` marks unsupported or legacy versions. + +## Next Steps + +- Read the [Qdrant CRD concept](/docs/guides/qdrant/concepts/qdrant.md). +- Run the [Qdrant quickstart](/docs/guides/qdrant/quickstart/quickstart.md). \ No newline at end of file diff --git a/docs/guides/qdrant/concepts/opsrequest.md b/docs/guides/qdrant/concepts/opsrequest.md new file mode 100644 index 000000000..867c51da3 --- /dev/null +++ b/docs/guides/qdrant/concepts/opsrequest.md @@ -0,0 +1,56 @@ +--- +title: QdrantOpsRequest CRD +menu: + docs_{{ .version }}: + identifier: qdrant-opsrequest-concepts + name: QdrantOpsRequest + parent: qdrant-concepts-qdrant + weight: 25 +menu_name: docs_{{ .version }} +section_menu_id: guides +--- + +> New to KubeDB? Please start [here](/docs/README.md). + +# QdrantOpsRequest + +## What is QdrantOpsRequest + +`QdrantOpsRequest` is the CRD for day-2 operational workflows for KubeDB-managed Qdrant databases. + +## Supported operation types + +- `Reconfigure` +- `ReconfigureTLS` +- `Restart` +- `RotateAuth` +- `UpdateVersion` +- `HorizontalScaling` +- `VerticalScaling` +- `VolumeExpansion` + +## Sample QdrantOpsRequest + +```yaml +apiVersion: ops.kubedb.com/v1alpha1 +kind: QdrantOpsRequest +metadata: + name: qdrant-restart + namespace: demo +spec: + type: Restart + databaseRef: + name: qdrant-sample +``` + +## Key fields + +- `spec.type` selects the operation category. +- `spec.databaseRef.name` identifies the target `Qdrant` object. +- Operation-specific fields are provided under keys like `updateVersion`, `horizontalScaling`, `verticalScaling`, or `volumeExpansion`. +- `spec.timeout` and `spec.apply` can control execution behavior where supported. + +## Next Steps + +- See [Qdrant ops overview](/docs/guides/qdrant/ops-request/overview.md) for operation links. +- Follow operation tutorials like [Restart](/docs/guides/qdrant/restart/restart.md) and [VolumeExpansion](/docs/guides/qdrant/volume-expansion/overview.md). \ No newline at end of file diff --git a/docs/guides/qdrant/concepts/qdrant.md b/docs/guides/qdrant/concepts/qdrant.md new file mode 100644 index 000000000..523e53ef1 --- /dev/null +++ b/docs/guides/qdrant/concepts/qdrant.md @@ -0,0 +1,51 @@ +--- +title: Qdrant CRD +menu: + docs_{{ .version }}: + identifier: qdrant-concepts-qdrant + name: Qdrant + parent: qdrant-concepts + weight: 10 +menu_name: docs_{{ .version }} +section_menu_id: guides +--- + +> New to KubeDB? Please start [here](/docs/README.md). + +# Qdrant + +## What is Qdrant + +`Qdrant` is a KubeDB CRD for managing Qdrant vector databases with Kubernetes-native APIs. + +## Qdrant Spec + +```yaml +apiVersion: kubedb.com/v1alpha2 +kind: Qdrant +metadata: + name: qdrant-sample + namespace: demo +spec: + version: 1.17.0 + mode: Distributed + replicas: 3 + storage: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 2Gi + deletionPolicy: WipeOut +``` + +### Key fields + +- `spec.version` points to a `QdrantVersion`. +- `spec.mode` supports `Standalone` and `Distributed`. +- `spec.replicas` controls the number of pods. +- `spec.storageType` and `spec.storage` configure persistence. +- `spec.tls` configures client and p2p TLS. +- `spec.authSecret` and `spec.disableSecurity` control authentication. +- `spec.monitor` integrates monitoring. +- `spec.deletionPolicy` controls delete behavior. \ No newline at end of file diff --git a/docs/guides/qdrant/configuration/_index.md b/docs/guides/qdrant/configuration/_index.md new file mode 100644 index 000000000..c18554252 --- /dev/null +++ b/docs/guides/qdrant/configuration/_index.md @@ -0,0 +1,10 @@ +--- +title: Run Qdrant with Custom Configuration +menu: + docs_{{ .version }}: + identifier: qdrant-configuration + name: Custom Configuration + parent: qdrant-guides + weight: 130 +menu_name: docs_{{ .version }} +--- \ No newline at end of file diff --git a/docs/guides/qdrant/configuration/using-config-file.md b/docs/guides/qdrant/configuration/using-config-file.md new file mode 100644 index 000000000..743a319a9 --- /dev/null +++ b/docs/guides/qdrant/configuration/using-config-file.md @@ -0,0 +1,90 @@ +--- +title: Run Qdrant with Custom Configuration +menu: + docs_{{ .version }}: + identifier: qdrant-using-config-file + name: Config File + parent: qdrant-configuration + weight: 10 +menu_name: docs_{{ .version }} +section_menu_id: guides +--- + +> New to KubeDB? Please start [here](/docs/README.md). + +# Using Custom Configuration File + +KubeDB uses `spec.configuration.secretName` to provide a custom Qdrant configuration. + +## Before You Begin + +- You need a Kubernetes cluster and the `kubectl` CLI configured for that cluster. +- Install KubeDB operator following [the setup guide](/docs/setup/README.md). + +```bash +$ kubectl create ns demo +namespace/demo created +``` + +## Create Configuration Secret + +Create a `production.yaml` file with your desired runtime settings: + +```yaml +log_level: INFO +service: + max_request_size_mb: 32 +``` + +Create a Secret from this file: + +```bash +$ kubectl create secret generic -n demo qdrant-config \ + --from-file=production.yaml=./production.yaml +secret/qdrant-config created +``` + +## Deploy Qdrant with Custom Configuration + +```yaml +apiVersion: kubedb.com/v1alpha2 +kind: Qdrant +metadata: + name: custom-qdrant + namespace: demo +spec: + version: 1.17.0 + mode: Distributed + replicas: 3 + configuration: + secretName: qdrant-config + storageType: Durable + storage: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 2Gi + deletionPolicy: WipeOut +``` + +```bash +$ kubectl apply -f qdrant-configuration.yaml +qdrant.kubedb.com/custom-qdrant created +``` + +## Verify + +```bash +$ kubectl get qdrant -n demo custom-qdrant +NAME VERSION STATUS AGE +custom-qdrant 1.17.0 Ready 2m +``` + +## Cleaning up + +```bash +kubectl delete qdrant -n demo custom-qdrant +kubectl delete secret -n demo qdrant-config +kubectl delete ns demo +``` \ No newline at end of file diff --git a/docs/guides/qdrant/custom-rbac/_index.md b/docs/guides/qdrant/custom-rbac/_index.md new file mode 100644 index 000000000..6807300eb --- /dev/null +++ b/docs/guides/qdrant/custom-rbac/_index.md @@ -0,0 +1,10 @@ +--- +title: Run Qdrant with Custom RBAC resources +menu: + docs_{{ .version }}: + identifier: qdrant-custom-rbac + name: Custom RBAC + parent: qdrant-guides + weight: 90 +menu_name: docs_{{ .version }} +--- \ No newline at end of file diff --git a/docs/guides/qdrant/custom-rbac/using-custom-rbac.md b/docs/guides/qdrant/custom-rbac/using-custom-rbac.md new file mode 100644 index 000000000..0a300d04f --- /dev/null +++ b/docs/guides/qdrant/custom-rbac/using-custom-rbac.md @@ -0,0 +1,127 @@ +--- +title: Run Qdrant with Custom RBAC resources +menu: + docs_{{ .version }}: + identifier: qdrant-custom-rbac-quickstart + name: Custom RBAC + parent: qdrant-custom-rbac + weight: 10 +menu_name: docs_{{ .version }} +section_menu_id: guides +--- + +> New to KubeDB? Please start [here](/docs/README.md). + +# Using Custom RBAC Resources + +This tutorial shows how to run Qdrant with custom `ServiceAccount`, `Role`, and `RoleBinding` resources. + +## Before You Begin + +- You need a Kubernetes cluster and the `kubectl` CLI configured for that cluster. +- Install KubeDB operator following [the setup guide](/docs/setup/README.md). +- This tutorial uses `demo` namespace. + +```bash +$ kubectl create ns demo +namespace/demo created +``` + +## Create Custom RBAC Resources + +Create the service account first: + +```bash +$ kubectl create serviceaccount -n demo my-custom-serviceaccount +serviceaccount/my-custom-serviceaccount created +``` + +Create a role with the required permissions for Qdrant Pods and related resources: + +```yaml +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + name: my-custom-role + namespace: demo +rules: +- apiGroups: + - kubedb.com + resources: + - qdrants + resourceNames: + - qdrant-custom-rbac + verbs: + - get +- apiGroups: + - "" + resources: + - pods + - pods/exec + verbs: + - get + - list + - create +``` + +Bind the role to the service account: + +```bash +$ kubectl create rolebinding my-custom-rolebinding \ + --role=my-custom-role \ + --serviceaccount=demo:my-custom-serviceaccount \ + --namespace=demo +rolebinding.rbac.authorization.k8s.io/my-custom-rolebinding created +``` + +## Deploy Qdrant with Custom Service Account + +## Deploy Qdrant with Custom Service Account + +```yaml +apiVersion: kubedb.com/v1alpha2 +kind: Qdrant +metadata: + name: qdrant-custom-rbac + namespace: demo +spec: + version: 1.17.0 + mode: Distributed + replicas: 3 + podTemplate: + spec: + serviceAccountName: my-custom-serviceaccount + storageType: Durable + storage: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 2Gi + deletionPolicy: WipeOut +``` + +```bash +$ kubectl apply -f qdrant-custom-rbac.yaml +qdrant.kubedb.com/qdrant-custom-rbac created +``` + +## Verify + +```bash +$ kubectl get qdrant -n demo qdrant-custom-rbac +NAME VERSION STATUS AGE +qdrant-custom-rbac 1.17.0 Ready 2m + +$ kubectl get pod -n demo -l app.kubernetes.io/instance=qdrant-custom-rbac +``` + +## Cleaning up + +```bash +kubectl delete qdrant -n demo qdrant-custom-rbac +kubectl delete rolebinding -n demo my-custom-rolebinding +kubectl delete role -n demo my-custom-role +kubectl delete serviceaccount -n demo my-custom-serviceaccount +kubectl delete ns demo +``` \ No newline at end of file diff --git a/docs/guides/qdrant/monitoring/_index.md b/docs/guides/qdrant/monitoring/_index.md new file mode 100644 index 000000000..ab29e3dc2 --- /dev/null +++ b/docs/guides/qdrant/monitoring/_index.md @@ -0,0 +1,10 @@ +--- +title: Qdrant Monitoring +menu: + docs_{{ .version }}: + identifier: qdrant-monitoring + name: Monitoring + parent: qdrant-guides + weight: 20 +menu_name: docs_{{ .version }} +--- diff --git a/docs/guides/qdrant/monitoring/overview.md b/docs/guides/qdrant/monitoring/overview.md new file mode 100644 index 000000000..fde86dc74 --- /dev/null +++ b/docs/guides/qdrant/monitoring/overview.md @@ -0,0 +1,39 @@ +--- +title: Qdrant Monitoring Overview +menu: + docs_{{ .version }}: + identifier: qdrant-monitoring-overview + name: Overview + parent: qdrant-monitoring + weight: 10 +menu_name: docs_{{ .version }} +section_menu_id: guides +--- + +# Qdrant Monitoring + +This guide shows how to enable and verify monitoring for Qdrant. + +## Before You Begin + +- Deploy Qdrant first using the [quickstart guide](/docs/guides/qdrant/quickstart/quickstart.md). +- Install Prometheus Operator or another compatible metrics stack. + +## Enable Monitoring + +Qdrant monitoring can be configured through `spec.monitor`. + +- Enable metric scraping and verify health endpoints. +- Track shard status and node health in distributed mode. + +## Verify + +```bash +kubectl get qdrant -n demo qdrant-sample -o yaml +kubectl get servicemonitor -A +``` + +## Next Steps + +- Add dashboards for shard balance, request latency, and storage growth. +- Recheck metrics after scaling or version updates. diff --git a/docs/guides/qdrant/monitoring/using-builtin-prometheus.md b/docs/guides/qdrant/monitoring/using-builtin-prometheus.md new file mode 100644 index 000000000..3f6a1f02a --- /dev/null +++ b/docs/guides/qdrant/monitoring/using-builtin-prometheus.md @@ -0,0 +1,73 @@ +--- +title: Monitor Qdrant using Builtin Prometheus Discovery +menu: + docs_{{ .version }}: + identifier: qdrant-using-builtin-prometheus-monitoring + name: Builtin Prometheus + parent: qdrant-monitoring + weight: 20 +menu_name: docs_{{ .version }} +section_menu_id: guides +--- + +> New to KubeDB? Please start [here](/docs/README.md). + +# Monitoring Qdrant with Builtin Prometheus + +This guide shows how to enable builtin Prometheus scraping for Qdrant. + +## Before You Begin + +- Install KubeDB operator from [setup guide](/docs/setup/README.md). +- Use separate namespaces for database and monitoring resources. + +```bash +$ kubectl create ns demo +$ kubectl create ns monitoring +``` + +## Deploy Qdrant with Monitoring Enabled + +```yaml +apiVersion: kubedb.com/v1alpha2 +kind: Qdrant +metadata: + name: builtin-prom-qdrant + namespace: demo +spec: + version: 1.17.0 + mode: Distributed + replicas: 3 + storageType: Durable + storage: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 2Gi + monitor: + agent: prometheus.io/builtin + deletionPolicy: WipeOut +``` + +```bash +$ kubectl apply -f builtin-prom-qdrant.yaml +qdrant.kubedb.com/builtin-prom-qdrant created +``` + +## Verify + +```bash +$ kubectl get qdrant -n demo builtin-prom-qdrant +$ kubectl get svc -n demo --selector=app.kubernetes.io/instance=builtin-prom-qdrant +``` + +The operator creates a stats service with scrape annotations for builtin Prometheus discovery. + +## Cleaning up + +```bash +kubectl delete qdrant -n demo builtin-prom-qdrant +kubectl delete ns demo +kubectl delete ns monitoring +``` \ No newline at end of file diff --git a/docs/guides/qdrant/monitoring/using-prometheus-operator.md b/docs/guides/qdrant/monitoring/using-prometheus-operator.md new file mode 100644 index 000000000..9935b68d4 --- /dev/null +++ b/docs/guides/qdrant/monitoring/using-prometheus-operator.md @@ -0,0 +1,76 @@ +--- +title: Monitor Qdrant using Prometheus Operator +menu: + docs_{{ .version }}: + identifier: qdrant-using-prometheus-operator-monitoring + name: Prometheus Operator + parent: qdrant-monitoring + weight: 30 +menu_name: docs_{{ .version }} +section_menu_id: guides +--- + +> New to KubeDB? Please start [here](/docs/README.md). + +# Monitoring Qdrant using Prometheus Operator + +This guide shows how to expose Qdrant metrics using Prometheus Operator integration. + +## Before You Begin + +- Install KubeDB operator from [setup guide](/docs/setup/README.md). +- Ensure Prometheus Operator is installed in your cluster. + +```bash +$ kubectl create ns demo +$ kubectl create ns monitoring +``` + +## Deploy Qdrant with Monitoring Enabled + +```yaml +apiVersion: kubedb.com/v1alpha2 +kind: Qdrant +metadata: + name: coreos-prom-qdrant + namespace: demo +spec: + version: 1.17.0 + mode: Distributed + replicas: 3 + storageType: Durable + storage: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 2Gi + monitor: + agent: prometheus.io/operator + prometheus: + serviceMonitor: + labels: + release: prometheus + interval: 10s + deletionPolicy: WipeOut +``` + +```bash +$ kubectl apply -f coreos-prom-qdrant.yaml +qdrant.kubedb.com/coreos-prom-qdrant created +``` + +## Verify + +```bash +$ kubectl get qdrant -n demo coreos-prom-qdrant +$ kubectl get servicemonitor -n demo +``` + +## Cleaning up + +```bash +kubectl delete qdrant -n demo coreos-prom-qdrant +kubectl delete ns demo +kubectl delete ns monitoring +``` \ No newline at end of file diff --git a/docs/guides/qdrant/ops-request/_index.md b/docs/guides/qdrant/ops-request/_index.md new file mode 100644 index 000000000..3a64a4ebe --- /dev/null +++ b/docs/guides/qdrant/ops-request/_index.md @@ -0,0 +1,10 @@ +--- +title: Qdrant Ops Request +menu: + docs_{{ .version }}: + identifier: qdrant-ops-request + name: Ops Request + parent: qdrant-guides + weight: 35 +menu_name: docs_{{ .version }} +--- diff --git a/docs/guides/qdrant/ops-request/overview.md b/docs/guides/qdrant/ops-request/overview.md new file mode 100644 index 000000000..e2e09057c --- /dev/null +++ b/docs/guides/qdrant/ops-request/overview.md @@ -0,0 +1,40 @@ +--- +title: Qdrant Ops Request Overview +menu: + docs_{{ .version }}: + identifier: qdrant-ops-request-overview + name: Overview + parent: qdrant-ops-request + weight: 10 +menu_name: docs_{{ .version }} +section_menu_id: guides +--- + +# Qdrant Ops Request + +This guide lists the Qdrant operations currently documented for KubeDB. + +## Before You Begin + +- Deploy Qdrant first using the [quickstart guide](/docs/guides/qdrant/quickstart/quickstart.md). +- Review the operation-specific pages before applying changes in production. + +## Supported Ops Requests + +- [HorizontalScaling](/docs/guides/qdrant/scaling/horizontal-scaling/overview.md) +- [Reconfigure](/docs/guides/qdrant/reconfigure/overview.md) +- [ReconfigureTLS](/docs/guides/qdrant/reconfigure-tls/overview.md) +- [Restart](/docs/guides/qdrant/restart/restart.md) +- [RotateAuth](/docs/guides/qdrant/rotate-auth/overview.md) +- [VerticalScaling](/docs/guides/qdrant/scaling/vertical-scaling/overview.md) +- [VolumeExpansion](/docs/guides/qdrant/volume-expansion/overview.md) +- [UpdateVersion](/docs/guides/qdrant/update-version/overview.md) + +## How Ops Requests Work + +Create a `QdrantOpsRequest` for the target database, wait for the request to complete, and verify both the request object and the database status before moving to the next change. + +## Next Steps + +- Choose the specific operation page that matches your intended change. +- Apply one operation at a time and wait for completion before starting the next. diff --git a/docs/guides/qdrant/private-registry/_index.md b/docs/guides/qdrant/private-registry/_index.md new file mode 100644 index 000000000..b8928dd38 --- /dev/null +++ b/docs/guides/qdrant/private-registry/_index.md @@ -0,0 +1,10 @@ +--- +title: Run Qdrant using Private Registry +menu: + docs_{{ .version }}: + identifier: qdrant-private-registry + name: Private Registry + parent: qdrant-guides + weight: 120 +menu_name: docs_{{ .version }} +--- \ No newline at end of file diff --git a/docs/guides/qdrant/private-registry/using-private-registry.md b/docs/guides/qdrant/private-registry/using-private-registry.md new file mode 100644 index 000000000..ba7f32ad5 --- /dev/null +++ b/docs/guides/qdrant/private-registry/using-private-registry.md @@ -0,0 +1,81 @@ +--- +title: Run Qdrant using Private Registry +menu: + docs_{{ .version }}: + identifier: qdrant-using-private-registry + name: Quickstart + parent: qdrant-private-registry + weight: 10 +menu_name: docs_{{ .version }} +section_menu_id: guides +--- + +> New to KubeDB? Please start [here](/docs/README.md). + +# Using Private Docker Registry + +This tutorial shows how to run KubeDB managed Qdrant using private Docker images. + +## Before You Begin + +- Prepare a Kubernetes cluster and `kubectl`. + +```bash +kubectl create ns demo +``` + +## Create ImagePullSecret + +```bash +kubectl create secret docker-registry -n demo myregistrykey \ + --docker-server=DOCKER_REGISTRY_SERVER \ + --docker-username=DOCKER_USER \ + --docker-email=DOCKER_EMAIL \ + --docker-password=DOCKER_PASSWORD +``` + +## Create QdrantVersion CRD + +```yaml +apiVersion: catalog.kubedb.com/v1alpha1 +kind: QdrantVersion +metadata: + name: "1.17.0" +spec: + db: + image: PRIVATE_REGISTRY/qdrant:1.17.0 + version: "1.17.0" +``` + +## Deploy Qdrant from Private Registry + +```yaml +apiVersion: kubedb.com/v1alpha2 +kind: Qdrant +metadata: + name: pvt-reg-qdrant + namespace: demo +spec: + version: 1.17.0 + mode: Distributed + replicas: 3 + storageType: Durable + storage: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 2Gi + podTemplate: + spec: + imagePullSecrets: + - name: myregistrykey + deletionPolicy: WipeOut +``` + +## Cleaning up + +```bash +kubectl delete qdrant -n demo pvt-reg-qdrant +kubectl delete ns demo +``` \ No newline at end of file diff --git a/docs/guides/qdrant/quickstart/_index.md b/docs/guides/qdrant/quickstart/_index.md new file mode 100644 index 000000000..5a0211fd2 --- /dev/null +++ b/docs/guides/qdrant/quickstart/_index.md @@ -0,0 +1,10 @@ +--- +title: Qdrant Quickstart +menu: + docs_{{ .version }}: + identifier: qdrant-quickstart + name: Quickstart + parent: qdrant-guides + weight: 15 +menu_name: docs_{{ .version }} +--- \ No newline at end of file diff --git a/docs/guides/qdrant/quickstart/quickstart.md b/docs/guides/qdrant/quickstart/quickstart.md new file mode 100644 index 000000000..cc52f66cc --- /dev/null +++ b/docs/guides/qdrant/quickstart/quickstart.md @@ -0,0 +1,84 @@ +--- +title: Qdrant Quickstart +menu: + docs_{{ .version }}: + identifier: qdrant-quickstart-overview + name: Overview + parent: qdrant-quickstart + weight: 10 +menu_name: docs_{{ .version }} +section_menu_id: guides +--- + +> New to KubeDB? Please start [here](/docs/README.md). + +# Running Qdrant + +This tutorial shows how to run Qdrant with KubeDB. + +> Note: YAML files used in this tutorial are stored in [docs/examples/qdrant/quickstart](https://github.com/kubedb/docs/tree/{{< param "info.version" >}}/docs/examples/qdrant/quickstart). + +## Before You Begin + +- Prepare a Kubernetes cluster and `kubectl`. +- Install KubeDB from [/docs/setup/README.md](/docs/setup/README.md). +- This tutorial uses `docs/examples/qdrant/quickstart/distributed.yaml` as the working example manifest. +- Create namespace: + +```bash +kubectl create ns demo +``` + +## Check Available StorageClass + +```bash +kubectl get storageclass +``` + +## Check Available QdrantVersion + +```bash +kubectl get qdrantversions +``` + +## Create a Qdrant Database + +```yaml +apiVersion: kubedb.com/v1alpha2 +kind: Qdrant +metadata: + name: qdrant-sample + namespace: demo +spec: + version: 1.17.0 + mode: Distributed + replicas: 3 + storage: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 2Gi + deletionPolicy: WipeOut +``` + +```bash +kubectl apply -f https://github.com/kubedb/docs/raw/{{< param "info.version" >}}/docs/examples/qdrant/quickstart/distributed.yaml +kubectl get qdrant -n demo qdrant-sample -w +``` + +## Verify Qdrant Database + +```bash +kubectl get qdrant -n demo +kubectl describe qdrant -n demo qdrant-sample +``` + +When `status.phase` becomes `Ready`, the Qdrant cluster is ready to accept vector search and management requests. + +## Cleaning up + +```bash +kubectl delete qdrant -n demo qdrant-sample +kubectl delete ns demo +``` \ No newline at end of file diff --git a/docs/guides/qdrant/quickstart/rbac.md b/docs/guides/qdrant/quickstart/rbac.md new file mode 100644 index 000000000..ab69353ce --- /dev/null +++ b/docs/guides/qdrant/quickstart/rbac.md @@ -0,0 +1,60 @@ +--- +title: Qdrant RBAC +menu: + docs_{{ .version }}: + identifier: qdrant-quickstart-rbac + name: RBAC + parent: qdrant-quickstart + weight: 20 +menu_name: docs_{{ .version }} +section_menu_id: guides +--- + +> New to KubeDB? Please start [here](/docs/README.md). + +# Run Qdrant with RBAC Enabled + +This tutorial shows how to run Qdrant with the RBAC permissions required by KubeDB. + +## Before You Begin + +- Prepare a Kubernetes cluster and `kubectl`. +- Install KubeDB from [/docs/setup/README.md](/docs/setup/README.md). + +```bash +kubectl create ns demo +``` + +## Deploy Qdrant + +```yaml +apiVersion: kubedb.com/v1alpha2 +kind: Qdrant +metadata: + name: qdrant-rbac + namespace: demo +spec: + version: 1.17.0 + mode: Distributed + replicas: 3 + storageType: Durable + storage: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 2Gi + deletionPolicy: WipeOut +``` + +```bash +kubectl apply -f https://github.com/kubedb/docs/raw/{{< param "info.version" >}}/docs/examples/qdrant/quickstart/distributed.yaml +kubectl get qdrant -n demo qdrant-rbac -w +``` + +## Cleaning up + +```bash +kubectl delete qdrant -n demo qdrant-rbac +kubectl delete ns demo +``` \ No newline at end of file diff --git a/docs/guides/qdrant/reconfigure-tls/_index.md b/docs/guides/qdrant/reconfigure-tls/_index.md new file mode 100644 index 000000000..75e1236e8 --- /dev/null +++ b/docs/guides/qdrant/reconfigure-tls/_index.md @@ -0,0 +1,10 @@ +--- +title: Reconfigure TLS +menu: + docs_{{ .version }}: + identifier: qdrant-reconfigure-tls + name: Reconfigure TLS + parent: qdrant-guides + weight: 33 +menu_name: docs_{{ .version }} +--- diff --git a/docs/guides/qdrant/reconfigure-tls/overview.md b/docs/guides/qdrant/reconfigure-tls/overview.md new file mode 100644 index 000000000..1d2484431 --- /dev/null +++ b/docs/guides/qdrant/reconfigure-tls/overview.md @@ -0,0 +1,55 @@ +--- +title: Reconfiguring Qdrant TLS +menu: + docs_{{ .version }}: + identifier: qdrant-reconfigure-tls-overview + name: Overview + parent: qdrant-reconfigure-tls + weight: 10 +menu_name: docs_{{ .version }} +section_menu_id: guides +--- + +> New to KubeDB? Please start [here](/docs/README.md). + +# Reconfigure TLS for Qdrant + +This guide shows how to rotate or reconfigure TLS materials for Qdrant. + +## Before You Begin + +- Install `cert-manager` in your cluster. +- Ensure KubeDB and Ops-manager are installed. +- Use the example files from `docs/examples/qdrant/quickstart/distributed.yaml` and `docs/examples/qdrant/reconfigure-tls/ops-request.yaml`. + +```bash +kubectl create ns demo +``` + +## Deploy Qdrant + +```bash +kubectl apply -f https://github.com/kubedb/docs/raw/{{< param "info.version" >}}/docs/examples/qdrant/quickstart/distributed.yaml +kubectl get qdrant -n demo qdrant-sample -w +``` + +## Apply ReconfigureTLS OpsRequest + +```bash +kubectl apply -f https://github.com/kubedb/docs/raw/{{< param "info.version" >}}/docs/examples/qdrant/reconfigure-tls/ops-request.yaml +kubectl get qdrantopsrequest -n demo qdrant-reconfigure-tls +``` + +## Verify + +```bash +kubectl describe qdrantopsrequest -n demo qdrant-reconfigure-tls +``` + +## Cleaning up + +```bash +kubectl delete qdrantopsrequest -n demo qdrant-reconfigure-tls +kubectl delete qdrant -n demo qdrant-sample +kubectl delete ns demo +``` diff --git a/docs/guides/qdrant/reconfigure-tls/reconfigure-tls.md b/docs/guides/qdrant/reconfigure-tls/reconfigure-tls.md new file mode 100644 index 000000000..4e9b63721 --- /dev/null +++ b/docs/guides/qdrant/reconfigure-tls/reconfigure-tls.md @@ -0,0 +1,63 @@ +--- +title: Reconfigure TLS of Qdrant +menu: + docs_{{ .version }}: + identifier: qdrant-reconfigure-tls-cluster + name: Cluster + parent: qdrant-reconfigure-tls + weight: 30 +menu_name: docs_{{ .version }} +section_menu_id: guides +--- + +> New to KubeDB? Please start [here](/docs/README.md). + +# Reconfigure TLS in Qdrant + +This guide shows how to reconfigure or rotate TLS certificates of a Qdrant database using `QdrantOpsRequest`. + +## Before You Begin + +- Install KubeDB Community and Enterprise operators. +- Ensure your database is already running with TLS enabled. + +```bash +$ kubectl create ns demo +``` + +## Apply ReconfigureTLS OpsRequest + +```yaml +apiVersion: ops.kubedb.com/v1alpha1 +kind: QdrantOpsRequest +metadata: + name: qdrant-reconfigure-tls + namespace: demo +spec: + type: ReconfigureTLS + databaseRef: + name: tls-qdrant + tls: + rotateCertificates: true + client: true + p2p: true +``` + + ```bash + $ kubectl apply -f qdrant-reconfigure-tls.yaml + qdrantopsrequest.ops.kubedb.com/qdrant-reconfigure-tls created + ``` + + ## Verify + + ```bash + $ kubectl get qdrantopsrequest -n demo qdrant-reconfigure-tls + $ kubectl describe qdrantopsrequest -n demo qdrant-reconfigure-tls + ``` + + ## Cleaning up + + ```bash + kubectl delete qdrantopsrequest -n demo qdrant-reconfigure-tls + kubectl delete ns demo + ``` \ No newline at end of file diff --git a/docs/guides/qdrant/reconfigure/_index.md b/docs/guides/qdrant/reconfigure/_index.md new file mode 100644 index 000000000..fe61847fb --- /dev/null +++ b/docs/guides/qdrant/reconfigure/_index.md @@ -0,0 +1,10 @@ +--- +title: Reconfigure +menu: + docs_{{ .version }}: + identifier: qdrant-reconfigure + name: Reconfigure + parent: qdrant-guides + weight: 32 +menu_name: docs_{{ .version }} +--- diff --git a/docs/guides/qdrant/reconfigure/overview.md b/docs/guides/qdrant/reconfigure/overview.md new file mode 100644 index 000000000..d35656deb --- /dev/null +++ b/docs/guides/qdrant/reconfigure/overview.md @@ -0,0 +1,54 @@ +--- +title: Reconfiguring Qdrant +menu: + docs_{{ .version }}: + identifier: qdrant-reconfigure-overview + name: Overview + parent: qdrant-reconfigure + weight: 10 +menu_name: docs_{{ .version }} +section_menu_id: guides +--- + +> New to KubeDB? Please start [here](/docs/README.md). + +# Reconfiguring Qdrant + +This guide shows how to update runtime configuration of Qdrant using `QdrantOpsRequest`. + +## Before You Begin + +- Ensure KubeDB and Ops-manager are installed. +- Use the example files from `docs/examples/qdrant/quickstart/distributed.yaml` and `docs/examples/qdrant/reconfigure/ops-request.yaml`. + +```bash +kubectl create ns demo +``` + +## Deploy Qdrant + +```bash +kubectl apply -f https://github.com/kubedb/docs/raw/{{< param "info.version" >}}/docs/examples/qdrant/quickstart/distributed.yaml +kubectl get qdrant -n demo qdrant-sample -w +``` + +## Apply Reconfigure OpsRequest + +```bash +kubectl apply -f https://github.com/kubedb/docs/raw/{{< param "info.version" >}}/docs/examples/qdrant/reconfigure/ops-request.yaml +kubectl get qdrantopsrequest -n demo qdrant-reconfigure +``` + +## Verify + +```bash +kubectl describe qdrantopsrequest -n demo qdrant-reconfigure +``` + +## Cleaning up + +```bash +kubectl delete qdrantopsrequest -n demo qdrant-reconfigure +kubectl delete qdrant -n demo qdrant-sample +kubectl delete ns demo +``` diff --git a/docs/guides/qdrant/reconfigure/reconfigure.md b/docs/guides/qdrant/reconfigure/reconfigure.md new file mode 100644 index 000000000..fa1df1d3c --- /dev/null +++ b/docs/guides/qdrant/reconfigure/reconfigure.md @@ -0,0 +1,62 @@ +--- +title: Reconfigure Qdrant +menu: + docs_{{ .version }}: + identifier: qdrant-reconfigure-cluster + name: Cluster + parent: qdrant-reconfigure + weight: 30 +menu_name: docs_{{ .version }} +section_menu_id: guides +--- + +> New to KubeDB? Please start [here](/docs/README.md). + +# Reconfigure Qdrant + +This guide shows how to apply configuration changes to a running Qdrant database using `QdrantOpsRequest`. + +## Before You Begin + +- Install KubeDB Community and Enterprise operators from [setup guide](/docs/setup/README.md). +- Deploy a Qdrant database and ensure it is in `Ready` state. + +```bash +$ kubectl create ns demo +``` + +## Apply Reconfigure OpsRequest + +```yaml +apiVersion: ops.kubedb.com/v1alpha1 +kind: QdrantOpsRequest +metadata: + name: qdrant-reconfigure + namespace: demo +spec: + type: Reconfigure + databaseRef: + name: qdrant-sample + configuration: + configSecret: + name: qdrant-config-updated +``` + +```bash +$ kubectl apply -f qdrant-reconfigure.yaml +qdrantopsrequest.ops.kubedb.com/qdrant-reconfigure created +``` + +## Verify + +```bash +$ kubectl get qdrantopsrequest -n demo qdrant-reconfigure +$ kubectl describe qdrantopsrequest -n demo qdrant-reconfigure +``` + +## Cleaning up + +```bash +kubectl delete qdrantopsrequest -n demo qdrant-reconfigure +kubectl delete ns demo +``` \ No newline at end of file diff --git a/docs/guides/qdrant/restart/_index.md b/docs/guides/qdrant/restart/_index.md new file mode 100644 index 000000000..6c3b1e209 --- /dev/null +++ b/docs/guides/qdrant/restart/_index.md @@ -0,0 +1,10 @@ +--- +title: Restart Qdrant +menu: + docs_{{ .version }}: + identifier: qdrant-restart + name: Restart + parent: qdrant-guides + weight: 31 +menu_name: docs_{{ .version }} +--- diff --git a/docs/guides/qdrant/restart/restart.md b/docs/guides/qdrant/restart/restart.md new file mode 100644 index 000000000..d04dccfde --- /dev/null +++ b/docs/guides/qdrant/restart/restart.md @@ -0,0 +1,55 @@ +--- +title: Restart Qdrant +menu: + docs_{{ .version }}: + identifier: qdrant-restart-overview + name: Restart Qdrant + parent: qdrant-restart + weight: 10 +menu_name: docs_{{ .version }} +section_menu_id: guides +--- + +> New to KubeDB? Please start [here](/docs/README.md). + +# Restart Qdrant + +This guide shows how to restart Qdrant pods using `QdrantOpsRequest`. + +## Before You Begin + +- Ensure KubeDB and Ops-manager are installed. +- Use the example files from `docs/examples/qdrant/quickstart/distributed.yaml` and `docs/examples/qdrant/restart/ops-request.yaml`. +- Use a separate namespace: + +```bash +kubectl create ns demo +``` + +## Deploy Qdrant + +```bash +kubectl apply -f https://github.com/kubedb/docs/raw/{{< param "info.version" >}}/docs/examples/qdrant/quickstart/distributed.yaml +kubectl get qdrant -n demo qdrant-sample -w +``` + +## Apply Restart OpsRequest + +```bash +kubectl apply -f https://github.com/kubedb/docs/raw/{{< param "info.version" >}}/docs/examples/qdrant/restart/ops-request.yaml +kubectl get qdrantopsrequest -n demo qdrant-restart +``` + +## Verify + +```bash +kubectl describe qdrantopsrequest -n demo qdrant-restart +``` + +## Cleaning up + +```bash +kubectl delete qdrantopsrequest -n demo qdrant-restart +kubectl delete qdrant -n demo qdrant-sample +kubectl delete ns demo +``` diff --git a/docs/guides/qdrant/rotate-auth/_index.md b/docs/guides/qdrant/rotate-auth/_index.md new file mode 100644 index 000000000..2ee5055d5 --- /dev/null +++ b/docs/guides/qdrant/rotate-auth/_index.md @@ -0,0 +1,10 @@ +--- +title: Rotate Auth +menu: + docs_{{ .version }}: + identifier: qdrant-rotate-auth + name: Rotate Auth + parent: qdrant-guides + weight: 34 +menu_name: docs_{{ .version }} +--- diff --git a/docs/guides/qdrant/rotate-auth/overview.md b/docs/guides/qdrant/rotate-auth/overview.md new file mode 100644 index 000000000..4b483a982 --- /dev/null +++ b/docs/guides/qdrant/rotate-auth/overview.md @@ -0,0 +1,54 @@ +--- +title: Rotating Qdrant Credentials +menu: + docs_{{ .version }}: + identifier: qdrant-rotate-auth-overview + name: Overview + parent: qdrant-rotate-auth + weight: 10 +menu_name: docs_{{ .version }} +section_menu_id: guides +--- + +> New to KubeDB? Please start [here](/docs/README.md). + +# Rotate Auth for Qdrant + +This guide shows how to rotate Qdrant authentication credentials. + +## Before You Begin + +- Install KubeDB and Ops-manager from [here](/docs/setup/README.md). +- Use the example files from `docs/examples/qdrant/quickstart/distributed.yaml` and `docs/examples/qdrant/rotate-auth/ops-request.yaml`. + +```bash +kubectl create ns demo +``` + +## Deploy Qdrant + +```bash +kubectl apply -f https://github.com/kubedb/docs/raw/{{< param "info.version" >}}/docs/examples/qdrant/quickstart/distributed.yaml +kubectl get qdrant -n demo qdrant-sample -w +``` + +## Apply RotateAuth OpsRequest + +```bash +kubectl apply -f https://github.com/kubedb/docs/raw/{{< param "info.version" >}}/docs/examples/qdrant/rotate-auth/ops-request.yaml +kubectl get qdrantopsrequest -n demo qdrant-rotate-auth +``` + +## Verify + +```bash +kubectl describe qdrantopsrequest -n demo qdrant-rotate-auth +``` + +## Cleaning up + +```bash +kubectl delete qdrantopsrequest -n demo qdrant-rotate-auth +kubectl delete qdrant -n demo qdrant-sample +kubectl delete ns demo +``` diff --git a/docs/guides/qdrant/rotate-auth/rotateauth.md b/docs/guides/qdrant/rotate-auth/rotateauth.md new file mode 100644 index 000000000..c2658654c --- /dev/null +++ b/docs/guides/qdrant/rotate-auth/rotateauth.md @@ -0,0 +1,59 @@ +--- +title: Rotate Auth of Qdrant +menu: + docs_{{ .version }}: + identifier: qdrant-rotate-auth-cluster + name: Cluster + parent: qdrant-rotate-auth + weight: 30 +menu_name: docs_{{ .version }} +section_menu_id: guides +--- + +> New to KubeDB? Please start [here](/docs/README.md). + +# Rotate Auth for Qdrant + +This guide shows how to rotate authentication credentials for Qdrant using `QdrantOpsRequest`. + +## Before You Begin + +- Install KubeDB Community and Enterprise operators. +- Ensure your target `Qdrant` instance is `Ready`. + +```bash +$ kubectl create ns demo +``` + +## Apply RotateAuth OpsRequest + +```yaml +apiVersion: ops.kubedb.com/v1alpha1 +kind: QdrantOpsRequest +metadata: + name: qdrant-rotate-auth + namespace: demo +spec: + type: RotateAuth + databaseRef: + name: qdrant-sample +``` + +```bash +$ kubectl apply -f qdrant-rotate-auth.yaml +qdrantopsrequest.ops.kubedb.com/qdrant-rotate-auth created +``` + +## Verify + +```bash +$ kubectl get qdrantopsrequest -n demo qdrant-rotate-auth +$ kubectl describe qdrantopsrequest -n demo qdrant-rotate-auth +``` + +## Cleaning up + +```bash +kubectl delete qdrantopsrequest -n demo qdrant-rotate-auth +kubectl delete ns demo +``` \ No newline at end of file diff --git a/docs/guides/qdrant/scaling/_index.md b/docs/guides/qdrant/scaling/_index.md new file mode 100644 index 000000000..d9281f724 --- /dev/null +++ b/docs/guides/qdrant/scaling/_index.md @@ -0,0 +1,10 @@ +--- +title: Scaling +menu: + docs_{{ .version }}: + identifier: qdrant-scaling + name: Scaling + parent: qdrant-guides + weight: 37 +menu_name: docs_{{ .version }} +--- diff --git a/docs/guides/qdrant/scaling/horizontal-scaling/_index.md b/docs/guides/qdrant/scaling/horizontal-scaling/_index.md new file mode 100644 index 000000000..ed04b2b30 --- /dev/null +++ b/docs/guides/qdrant/scaling/horizontal-scaling/_index.md @@ -0,0 +1,10 @@ +--- +title: Horizontal Scaling +menu: + docs_{{ .version }}: + identifier: qdrant-horizontal-scaling + name: Horizontal Scaling + parent: qdrant-scaling + weight: 10 +menu_name: docs_{{ .version }} +--- diff --git a/docs/guides/qdrant/scaling/horizontal-scaling/overview.md b/docs/guides/qdrant/scaling/horizontal-scaling/overview.md new file mode 100644 index 000000000..a4f156cb7 --- /dev/null +++ b/docs/guides/qdrant/scaling/horizontal-scaling/overview.md @@ -0,0 +1,54 @@ +--- +title: Qdrant Horizontal Scaling Overview +menu: + docs_{{ .version }}: + identifier: qdrant-horizontal-scaling-overview + name: Overview + parent: qdrant-horizontal-scaling + weight: 10 +menu_name: docs_{{ .version }} +section_menu_id: guides +--- + +> New to KubeDB? Please start [here](/docs/README.md). + +# Qdrant Horizontal Scaling + +This guide shows how to scale Qdrant nodes horizontally. + +## Before You Begin + +- Ensure database is healthy (`status.phase=Ready`). +- Use the example files from `docs/examples/qdrant/quickstart/distributed.yaml` and `docs/examples/qdrant/scaling/horizontal-scaling/ops-request.yaml`. + +```bash +kubectl create ns demo +``` + +## Deploy Qdrant + +```bash +kubectl apply -f https://github.com/kubedb/docs/raw/{{< param "info.version" >}}/docs/examples/qdrant/quickstart/distributed.yaml +kubectl get qdrant -n demo qdrant-sample -w +``` + +## Apply HorizontalScaling OpsRequest + +```bash +kubectl apply -f https://github.com/kubedb/docs/raw/{{< param "info.version" >}}/docs/examples/qdrant/scaling/horizontal-scaling/ops-request.yaml +kubectl get qdrantopsrequest -n demo qdrant-horizontal-scale +``` + +## Verify + +```bash +kubectl describe qdrantopsrequest -n demo qdrant-horizontal-scale +``` + +## Cleaning up + +```bash +kubectl delete qdrantopsrequest -n demo qdrant-horizontal-scale +kubectl delete qdrant -n demo qdrant-sample +kubectl delete ns demo +``` diff --git a/docs/guides/qdrant/scaling/horizontal-scaling/scale-horizontally/index.md b/docs/guides/qdrant/scaling/horizontal-scaling/scale-horizontally/index.md new file mode 100644 index 000000000..50a03408f --- /dev/null +++ b/docs/guides/qdrant/scaling/horizontal-scaling/scale-horizontally/index.md @@ -0,0 +1,56 @@ +--- +title: Scale Qdrant Horizontally +menu: + docs_{{ .version }}: + identifier: qdrant-scale-horizontally + name: Scale Horizontally + parent: qdrant-horizontal-scaling + weight: 10 +menu_name: docs_{{ .version }} +section_menu_id: guides +--- + +> New to KubeDB? Please start [here](/docs/README.md). + +# Horizontal Scaling for Qdrant + +This guide shows how to scale Qdrant nodes horizontally using `QdrantOpsRequest`. + +## Before You Begin + +- Install KubeDB Community and Enterprise operators. +- Deploy Qdrant in distributed mode. + +## Apply HorizontalScaling OpsRequest + +```yaml +apiVersion: ops.kubedb.com/v1alpha1 +kind: QdrantOpsRequest +metadata: + name: qdrant-horizontal-scale + namespace: demo +spec: + type: HorizontalScaling + databaseRef: + name: qdrant-sample + horizontalScaling: + node: 5 +``` + +```bash +$ kubectl apply -f qdrant-horizontal-scale.yaml +qdrantopsrequest.ops.kubedb.com/qdrant-horizontal-scale created +``` + +## Verify + +```bash +$ kubectl get qdrantopsrequest -n demo qdrant-horizontal-scale +$ kubectl get pod -n demo -l app.kubernetes.io/instance=qdrant-sample +``` + +## Cleaning up + +```bash +kubectl delete qdrantopsrequest -n demo qdrant-horizontal-scale +``` \ No newline at end of file diff --git a/docs/guides/qdrant/scaling/vertical-scaling/_index.md b/docs/guides/qdrant/scaling/vertical-scaling/_index.md new file mode 100644 index 000000000..65d0c5aec --- /dev/null +++ b/docs/guides/qdrant/scaling/vertical-scaling/_index.md @@ -0,0 +1,10 @@ +--- +title: Vertical Scaling +menu: + docs_{{ .version }}: + identifier: qdrant-vertical-scaling + name: Vertical Scaling + parent: qdrant-scaling + weight: 20 +menu_name: docs_{{ .version }} +--- diff --git a/docs/guides/qdrant/scaling/vertical-scaling/overview.md b/docs/guides/qdrant/scaling/vertical-scaling/overview.md new file mode 100644 index 000000000..a22c83110 --- /dev/null +++ b/docs/guides/qdrant/scaling/vertical-scaling/overview.md @@ -0,0 +1,54 @@ +--- +title: Qdrant Vertical Scaling Overview +menu: + docs_{{ .version }}: + identifier: qdrant-vertical-scaling-overview + name: Overview + parent: qdrant-vertical-scaling + weight: 10 +menu_name: docs_{{ .version }} +section_menu_id: guides +--- + +> New to KubeDB? Please start [here](/docs/README.md). + +# Qdrant Vertical Scaling + +This guide shows how to update CPU and memory resources of Qdrant nodes. + +## Before You Begin + +- Ensure database is healthy and all pods are running. +- Use the example files from `docs/examples/qdrant/quickstart/distributed.yaml` and `docs/examples/qdrant/scaling/vertical-scaling/ops-request.yaml`. + +```bash +kubectl create ns demo +``` + +## Deploy Qdrant + +```bash +kubectl apply -f https://github.com/kubedb/docs/raw/{{< param "info.version" >}}/docs/examples/qdrant/quickstart/distributed.yaml +kubectl get qdrant -n demo qdrant-sample -w +``` + +## Apply VerticalScaling OpsRequest + +```bash +kubectl apply -f https://github.com/kubedb/docs/raw/{{< param "info.version" >}}/docs/examples/qdrant/scaling/vertical-scaling/ops-request.yaml +kubectl get qdrantopsrequest -n demo qdrant-vertical-scale +``` + +## Verify + +```bash +kubectl describe qdrantopsrequest -n demo qdrant-vertical-scale +``` + +## Cleaning up + +```bash +kubectl delete qdrantopsrequest -n demo qdrant-vertical-scale +kubectl delete qdrant -n demo qdrant-sample +kubectl delete ns demo +``` diff --git a/docs/guides/qdrant/scaling/vertical-scaling/scale-vertically/index.md b/docs/guides/qdrant/scaling/vertical-scaling/scale-vertically/index.md new file mode 100644 index 000000000..18b06c1cb --- /dev/null +++ b/docs/guides/qdrant/scaling/vertical-scaling/scale-vertically/index.md @@ -0,0 +1,57 @@ +--- +title: Scale Qdrant Vertically +menu: + docs_{{ .version }}: + identifier: qdrant-scale-vertically + name: Scale Vertically + parent: qdrant-vertical-scaling + weight: 10 +menu_name: docs_{{ .version }} +section_menu_id: guides +--- + +> New to KubeDB? Please start [here](/docs/README.md). + +# Vertical Scaling for Qdrant + +This guide shows how to change CPU and memory resources of Qdrant nodes using `QdrantOpsRequest`. + +## Before You Begin + +- Install KubeDB Community and Enterprise operators. +- Ensure database is healthy before applying scaling changes. + +## Apply VerticalScaling OpsRequest + +```yaml +apiVersion: ops.kubedb.com/v1alpha1 +kind: QdrantOpsRequest +metadata: + name: qdrant-vertical-scale + namespace: demo +spec: + type: VerticalScaling + databaseRef: + name: qdrant-sample + verticalScaling: + node: + resources: + requests: + cpu: "500m" + memory: "1Gi" + limits: + cpu: "1" + memory: "2Gi" +``` + +```bash +$ kubectl apply -f qdrant-vertical-scale.yaml +qdrantopsrequest.ops.kubedb.com/qdrant-vertical-scale created +``` + +## Verify + +```bash +$ kubectl get qdrantopsrequest -n demo qdrant-vertical-scale +$ kubectl describe qdrantopsrequest -n demo qdrant-vertical-scale +``` \ No newline at end of file diff --git a/docs/guides/qdrant/tls/_index.md b/docs/guides/qdrant/tls/_index.md new file mode 100644 index 000000000..2f261059d --- /dev/null +++ b/docs/guides/qdrant/tls/_index.md @@ -0,0 +1,10 @@ +--- +title: Qdrant TLS +menu: + docs_{{ .version }}: + identifier: qdrant-tls + name: TLS + parent: qdrant-guides + weight: 25 +menu_name: docs_{{ .version }} +--- diff --git a/docs/guides/qdrant/tls/configure/index.md b/docs/guides/qdrant/tls/configure/index.md new file mode 100644 index 000000000..a275556cb --- /dev/null +++ b/docs/guides/qdrant/tls/configure/index.md @@ -0,0 +1,70 @@ +--- +title: Configure TLS in Qdrant +menu: + docs_{{ .version }}: + identifier: qdrant-tls-configure + name: Configure TLS + parent: qdrant-tls + weight: 10 +menu_name: docs_{{ .version }} +section_menu_id: guides +--- + +> New to KubeDB? Please start [here](/docs/README.md). + +# Configure TLS in Qdrant + +This guide shows how to configure TLS for both client and peer-to-peer traffic in Qdrant. + +## Before You Begin + +- Install KubeDB operator. +- Install cert-manager and create an `Issuer` or `ClusterIssuer`. + +```bash +$ kubectl create ns demo +namespace/demo created +``` + +## Deploy Qdrant with TLS + +```yaml +apiVersion: kubedb.com/v1alpha2 +kind: Qdrant +metadata: + name: tls-qdrant + namespace: demo +spec: + version: 1.17.0 + mode: Distributed + replicas: 3 + storageType: Durable + storage: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 2Gi + tls: + issuerRef: + apiGroup: cert-manager.io + kind: Issuer + name: qdrant-issuer + client: true + p2p: true + deletionPolicy: WipeOut +``` + +## Verify + +```bash +$ kubectl get qdrant -n demo tls-qdrant +$ kubectl get secret -n demo | grep tls-qdrant +``` + +## Cleaning up + +```bash +kubectl delete qdrant -n demo tls-qdrant +kubectl delete ns demo +``` \ No newline at end of file diff --git a/docs/guides/qdrant/tls/overview.md b/docs/guides/qdrant/tls/overview.md new file mode 100644 index 000000000..6afca780c --- /dev/null +++ b/docs/guides/qdrant/tls/overview.md @@ -0,0 +1,39 @@ +--- +title: Qdrant TLS Overview +menu: + docs_{{ .version }}: + identifier: qdrant-tls-overview + name: Overview + parent: qdrant-tls + weight: 10 +menu_name: docs_{{ .version }} +section_menu_id: guides +--- + +# Qdrant TLS + +This guide shows the key TLS considerations for Qdrant. + +## Before You Begin + +- Install `cert-manager` in your cluster. +- Deploy Qdrant first using the [quickstart guide](/docs/guides/qdrant/quickstart/quickstart.md). + +## Configure TLS + +Qdrant TLS configuration is available via `spec.tls`. + +- Enable client and p2p certificate handling. +- Manage cert issuance using cert-manager issuer references. + +## Verify + +```bash +kubectl get qdrant -n demo qdrant-sample -o yaml +kubectl get secret -n demo +``` + +## Next Steps + +- Test both client and peer communication after enabling TLS. +- Use the dedicated [Reconfigure TLS guide](/docs/guides/qdrant/reconfigure-tls/overview.md) for certificate rotation. diff --git a/docs/guides/qdrant/update-version/_index.md b/docs/guides/qdrant/update-version/_index.md new file mode 100644 index 000000000..1baeebb52 --- /dev/null +++ b/docs/guides/qdrant/update-version/_index.md @@ -0,0 +1,10 @@ +--- +title: Update Version +menu: + docs_{{ .version }}: + identifier: qdrant-update-version + name: Update Version + parent: qdrant-guides + weight: 35 +menu_name: docs_{{ .version }} +--- diff --git a/docs/guides/qdrant/update-version/overview.md b/docs/guides/qdrant/update-version/overview.md new file mode 100644 index 000000000..0c31c3550 --- /dev/null +++ b/docs/guides/qdrant/update-version/overview.md @@ -0,0 +1,55 @@ +--- +title: Updating Qdrant Version +menu: + docs_{{ .version }}: + identifier: qdrant-update-version-overview + name: Overview + parent: qdrant-update-version + weight: 10 +menu_name: docs_{{ .version }} +section_menu_id: guides +--- + +> New to KubeDB? Please start [here](/docs/README.md). + +# Updating Qdrant Version + +This guide shows how to update Qdrant to a supported target version. + +## Before You Begin + +- Ensure Qdrant is `Ready` before submitting the update request. +- Use the example files from `docs/examples/qdrant/quickstart/distributed.yaml` and `docs/examples/qdrant/update-version/ops-request.yaml`. + +```bash +kubectl create ns demo +kubectl get qdrantversions +``` + +## Deploy Qdrant + +```bash +kubectl apply -f https://github.com/kubedb/docs/raw/{{< param "info.version" >}}/docs/examples/qdrant/quickstart/distributed.yaml +kubectl get qdrant -n demo qdrant-sample -w +``` + +## Apply UpdateVersion OpsRequest + +```bash +kubectl apply -f https://github.com/kubedb/docs/raw/{{< param "info.version" >}}/docs/examples/qdrant/update-version/ops-request.yaml +kubectl get qdrantopsrequest -n demo qdrant-update-version +``` + +## Verify + +```bash +kubectl describe qdrantopsrequest -n demo qdrant-update-version +``` + +## Cleaning up + +```bash +kubectl delete qdrantopsrequest -n demo qdrant-update-version +kubectl delete qdrant -n demo qdrant-sample +kubectl delete ns demo +``` diff --git a/docs/guides/qdrant/update-version/versionupgrading/index.md b/docs/guides/qdrant/update-version/versionupgrading/index.md new file mode 100644 index 000000000..4c068a07a --- /dev/null +++ b/docs/guides/qdrant/update-version/versionupgrading/index.md @@ -0,0 +1,54 @@ +--- +title: Upgrade Qdrant Version +menu: + docs_{{ .version }}: + identifier: qdrant-version-upgrading + name: Version Upgrading + parent: qdrant-update-version + weight: 10 +menu_name: docs_{{ .version }} +section_menu_id: guides +--- + +> New to KubeDB? Please start [here](/docs/README.md). + +# Upgrade Qdrant Version + +This guide shows how to upgrade Qdrant version using `QdrantOpsRequest`. + +## Before You Begin + +- Ensure target version exists in `QdrantVersion` catalog. +- Ensure the database is in `Ready` state. + +```bash +$ kubectl get qdrantversions +``` + +## Apply UpdateVersion OpsRequest + +```yaml +apiVersion: ops.kubedb.com/v1alpha1 +kind: QdrantOpsRequest +metadata: + name: qdrant-version-upgrade + namespace: demo +spec: + type: UpdateVersion + databaseRef: + name: qdrant-sample + updateVersion: + targetVersion: "1.18.0" +``` + +```bash +$ kubectl apply -f qdrant-version-upgrade.yaml +qdrantopsrequest.ops.kubedb.com/qdrant-version-upgrade created +``` + +## Verify + +```bash +$ kubectl get qdrantopsrequest -n demo qdrant-version-upgrade +$ kubectl get qdrant -n demo qdrant-sample +``` \ No newline at end of file diff --git a/docs/guides/qdrant/volume-expansion/_index.md b/docs/guides/qdrant/volume-expansion/_index.md new file mode 100644 index 000000000..b659e4426 --- /dev/null +++ b/docs/guides/qdrant/volume-expansion/_index.md @@ -0,0 +1,10 @@ +--- +title: Volume Expansion +menu: + docs_{{ .version }}: + identifier: qdrant-volume-expansion + name: Volume Expansion + parent: qdrant-guides + weight: 36 +menu_name: docs_{{ .version }} +--- diff --git a/docs/guides/qdrant/volume-expansion/overview.md b/docs/guides/qdrant/volume-expansion/overview.md new file mode 100644 index 000000000..1b581aacc --- /dev/null +++ b/docs/guides/qdrant/volume-expansion/overview.md @@ -0,0 +1,55 @@ +--- +title: Expanding Qdrant Storage +menu: + docs_{{ .version }}: + identifier: qdrant-volume-expansion-overview + name: Overview + parent: qdrant-volume-expansion + weight: 10 +menu_name: docs_{{ .version }} +section_menu_id: guides +--- + +> New to KubeDB? Please start [here](/docs/README.md). + +# Volume Expansion for Qdrant + +This guide shows how to increase PVC size of Qdrant data volumes. + +## Before You Begin + +- Ensure your StorageClass supports volume expansion. +- Use the example files from `docs/examples/qdrant/quickstart/distributed.yaml` and `docs/examples/qdrant/volume-expansion/ops-request.yaml`. + +```bash +kubectl create ns demo +kubectl get storageclass +``` + +## Deploy Qdrant + +```bash +kubectl apply -f https://github.com/kubedb/docs/raw/{{< param "info.version" >}}/docs/examples/qdrant/quickstart/distributed.yaml +kubectl get qdrant -n demo qdrant-sample -w +``` + +## Apply VolumeExpansion OpsRequest + +```bash +kubectl apply -f https://github.com/kubedb/docs/raw/{{< param "info.version" >}}/docs/examples/qdrant/volume-expansion/ops-request.yaml +kubectl get qdrantopsrequest -n demo qdrant-volume-expand +``` + +## Verify + +```bash +kubectl describe qdrantopsrequest -n demo qdrant-volume-expand +``` + +## Cleaning up + +```bash +kubectl delete qdrantopsrequest -n demo qdrant-volume-expand +kubectl delete qdrant -n demo qdrant-sample +kubectl delete ns demo +``` diff --git a/docs/guides/qdrant/volume-expansion/volume-expansion.md b/docs/guides/qdrant/volume-expansion/volume-expansion.md new file mode 100644 index 000000000..37f80d0d1 --- /dev/null +++ b/docs/guides/qdrant/volume-expansion/volume-expansion.md @@ -0,0 +1,55 @@ +--- +title: Expand Qdrant Volume +menu: + docs_{{ .version }}: + identifier: qdrant-volume-expansion-cluster + name: Cluster + parent: qdrant-volume-expansion + weight: 10 +menu_name: docs_{{ .version }} +section_menu_id: guides +--- + +> New to KubeDB? Please start [here](/docs/README.md). + +# Expand Qdrant Volume + +This guide shows how to expand persistent volume size for Qdrant nodes using `QdrantOpsRequest`. + +## Before You Begin + +- Your `StorageClass` must support volume expansion. +- Install KubeDB Community and Enterprise operators. + +```bash +$ kubectl get storageclass +``` + +## Apply VolumeExpansion OpsRequest + +```yaml +apiVersion: ops.kubedb.com/v1alpha1 +kind: QdrantOpsRequest +metadata: + name: qdrant-volume-expansion + namespace: demo +spec: + type: VolumeExpansion + databaseRef: + name: qdrant-sample + volumeExpansion: + mode: Online + node: 5Gi +``` + +```bash +$ kubectl apply -f qdrant-volume-expansion.yaml +qdrantopsrequest.ops.kubedb.com/qdrant-volume-expansion created +``` + +## Verify + +```bash +$ kubectl get qdrantopsrequest -n demo qdrant-volume-expansion +$ kubectl describe qdrantopsrequest -n demo qdrant-volume-expansion +``` \ No newline at end of file From 5f16c9b37d20f12be4a4453b93d88e802dd0988e Mon Sep 17 00:00:00 2001 From: Tamal Saha Date: Fri, 1 May 2026 11:46:26 +0600 Subject: [PATCH 02/10] docs(qdrant): expand guides and align overviews with postgres pattern Signed-off-by: Tamal Saha --- docs/guides/README.md | 7 + docs/guides/oracle/README.md | 5 + docs/guides/postgres/README.md | 1 + .../qdrant/autoscaler/compute/cluster.md | 229 ++++++++++++++- docs/guides/qdrant/autoscaler/overview.md | 39 ++- .../qdrant/autoscaler/storage/cluster.md | 218 +++++++++++++- docs/guides/qdrant/backup/overview.md | 71 ++--- docs/guides/qdrant/concepts/autoscaler.md | 87 +++++- docs/guides/qdrant/concepts/catalog.md | 46 ++- docs/guides/qdrant/concepts/opsrequest.md | 149 ++++++++-- docs/guides/qdrant/concepts/qdrant.md | 143 +++++++++- .../qdrant/configuration/using-config-file.md | 92 ++++-- .../qdrant/custom-rbac/using-custom-rbac.md | 142 ++++++++-- docs/guides/qdrant/monitoring/overview.md | 31 +- .../monitoring/using-builtin-prometheus.md | 112 +++++++- .../monitoring/using-prometheus-operator.md | 128 ++++++++- docs/guides/qdrant/ops-request/overview.md | 45 +-- .../using-private-registry.md | 88 +++++- docs/guides/qdrant/quickstart/quickstart.md | 178 ++++++++++-- docs/guides/qdrant/quickstart/rbac.md | 186 +++++++++++- .../guides/qdrant/reconfigure-tls/overview.md | 42 +-- .../qdrant/reconfigure-tls/reconfigure-tls.md | 235 +++++++++++++-- docs/guides/qdrant/reconfigure/overview.md | 45 ++- docs/guides/qdrant/reconfigure/reconfigure.md | 210 +++++++++++++- docs/guides/qdrant/restart/restart.md | 134 ++++++++- docs/guides/qdrant/rotate-auth/overview.md | 47 ++- docs/guides/qdrant/rotate-auth/rotateauth.md | 128 ++++++++- .../scaling/horizontal-scaling/overview.md | 45 ++- .../scale-horizontally/index.md | 204 ++++++++++++- .../scaling/vertical-scaling/overview.md | 45 ++- .../scale-vertically/index.md | 156 +++++++++- docs/guides/qdrant/tls/configure/index.md | 146 ++++++++-- docs/guides/qdrant/tls/overview.md | 42 +-- docs/guides/qdrant/update-version/overview.md | 46 ++- .../update-version/versionupgrading/index.md | 268 +++++++++++++++++- .../qdrant/volume-expansion/overview.md | 50 ++-- .../volume-expansion/volume-expansion.md | 221 ++++++++++++++- 37 files changed, 3459 insertions(+), 602 deletions(-) diff --git a/docs/guides/README.md b/docs/guides/README.md index 8f620422d..fae1fb6ab 100644 --- a/docs/guides/README.md +++ b/docs/guides/README.md @@ -18,25 +18,32 @@ aliases: Guides to show you how to perform tasks with KubeDB: - [Cassandra](/docs/guides/cassandra/README.md). Shows how to manage Cassandra using KubeDB. - [ClickHouse](/docs/guides/clickhouse/README.md). Shows how to manage ClickHouse using KubeDB. +- [DB2](/docs/guides/db2/README.md). Shows how to manage DB2 using KubeDB. +- [DocumentDB](/docs/guides/documentdb/README.md). Shows how to manage DocumentDB using KubeDB. - [Druid](/docs/guides/druid/README.md). Shows how to manage Druid using KubeDB. - [Elasticsearch](/docs/guides/elasticsearch/README.md). Shows how to manage Elasticsearch & OpenSearch using KubeDB. - [FerretDB](/docs/guides/ferretdb/README.md). Shows how to manage FerretDB using KubeDB. +- [HanaDB](/docs/guides/hanadb/README.md). Shows how to manage HanaDB using KubeDB. - [Hazelcast](/docs/guides/hazelcast/README.md). Shows how to manage Hazelcast using KubeDB. - [Ignite](/docs/guides/ignite/README.md). Shows how to manage Ignite using KubeDB. - [Kafka](/docs/guides/kafka/README.md). Shows how to manage Kafka using KubeDB. - [MariaDB](/docs/guides/mariadb). Shows how to manage MariaDB using KubeDB. - [Memcached](/docs/guides/memcached/README.md). Shows how to manage Memcached using KubeDB. +- [Milvus](/docs/guides/milvus/README.md). Shows how to manage Milvus using KubeDB. - [Microsoft SQL Server](/docs/guides/mssqlserver/README.md). Shows how to manage Microsoft SQL Server using KubeDB. - [MongoDB](/docs/guides/mongodb/README.md). Shows how to manage MongoDB using KubeDB. - [MySQL](/docs/guides/mysql/README.md). Shows how to manage MySQL using KubeDB. +- [Neo4j](/docs/guides/neo4j/README.md). Shows how to manage Neo4j using KubeDB. - [Oracle](/docs/guides/oracle/README.md). Shows how to manage Oracle using KubeDB. - [Percona XtraDB](/docs/guides/percona-xtradb/README.md). Shows how to manage Percona XtraDB using KubeDB. - [PgBouncer](/docs/guides/pgbouncer/README.md). Shows how to manage PgBouncer using KubeDB. - [Pgpool](/docs/guides/pgpool/README.md). Shows how to manage Pgpool using KubeDB. - [PostgreSQL](/docs/guides/postgres/README.md). Shows how to manage PostgreSQL using KubeDB. - [ProxySQL](/docs/guides/proxysql/README.md). Shows how to manage ProxySQL using KubeDB. +- [Qdrant](/docs/guides/qdrant/README.md). Shows how to manage Qdrant using KubeDB. - [RabbitMQ](/docs/guides/rabbitmq/README.md). Shows how to manage RabbitMQ using KubeDB. - [Redis](/docs/guides/redis/README.md). Shows how to manage Redis using KubeDB. - [SingleStore](/docs/guides/singlestore/README.md). Shows how to manage SingleStore using KubeDB. - [Solr](/docs/guides/solr/README.md). Shows how to manage Solr using KubeDB. +- [Weaviate](/docs/guides/weaviate/README.md). Shows how to manage Weaviate using KubeDB. - [ZooKeeper](/docs/guides/zookeeper/README.md). Shows how to manage ZooKeeper using KubeDB. diff --git a/docs/guides/oracle/README.md b/docs/guides/oracle/README.md index e6952f370..4e3ff000b 100644 --- a/docs/guides/oracle/README.md +++ b/docs/guides/oracle/README.md @@ -38,4 +38,9 @@ aliases: - [Quickstart Oracle](/docs/guides/oracle/quickstart/guide.md) with KubeDB Operator. +## Concepts + +- [Oracle CRD](/docs/guides/oracle/concepts/oracle.md) defines the schema for Oracle custom resource. +- [OracleVersion CRD](/docs/guides/oracle/concepts/catalog.md) defines the schema for OracleVersion custom resource. + diff --git a/docs/guides/postgres/README.md b/docs/guides/postgres/README.md index 3f15fda67..4cb93ed20 100644 --- a/docs/guides/postgres/README.md +++ b/docs/guides/postgres/README.md @@ -54,6 +54,7 @@ aliases: - Monitor your PostgreSQL database with KubeDB using [`out-of-the-box` Prometheus operator](/docs/guides/postgres/monitoring/using-prometheus-operator.md). - Use [private Docker registry](/docs/guides/postgres/private-registry/using-private-registry.md) to deploy PostgreSQL with KubeDB. - Detail concepts of [Postgres object](/docs/guides/postgres/concepts/postgres.md). +- Detail concepts of [PostgresAutoscaler object](/docs/guides/postgres/concepts/autoscaler.md). - Want to hack on KubeDB? Check our [contribution guidelines](/docs/CONTRIBUTING.md). diff --git a/docs/guides/qdrant/autoscaler/compute/cluster.md b/docs/guides/qdrant/autoscaler/compute/cluster.md index f37c7a9b7..14a8ef357 100644 --- a/docs/guides/qdrant/autoscaler/compute/cluster.md +++ b/docs/guides/qdrant/autoscaler/compute/cluster.md @@ -10,8 +10,231 @@ menu_name: docs_{{ .version }} section_menu_id: guides --- -# Qdrant Compute Autoscaler +> New to KubeDB? Please start [here](/docs/README.md). -This repository does not currently contain a `QdrantAutoscaler` Go type or CRD. +# Autoscaling the Compute Resource of a Qdrant Cluster -Because of that, there is no repository-backed compute autoscaler manifest to publish for Qdrant at this time. \ No newline at end of file +This guide will show you how to use `KubeDB` to auto-scale compute resources i.e. CPU and memory of a Qdrant cluster database. + +## Before You Begin + +- At first, you need to have a Kubernetes cluster, and the `kubectl` command-line tool must be configured to communicate with your cluster. If you do not already have a cluster, you can create one by using [kind](https://kind.sigs.k8s.io/docs/user/quick-start/). + +- Install `KubeDB` Community, Ops-Manager and Autoscaler operator in your cluster following the steps [here](/docs/setup/README.md). + +- Install `Metrics Server` from [here](https://github.com/kubernetes-sigs/metrics-server#installation). + +- You should be familiar with the following `KubeDB` concepts: + - [Qdrant](/docs/guides/qdrant/concepts/qdrant.md) + - [QdrantOpsRequest](/docs/guides/qdrant/concepts/opsrequest.md) + - [Compute Resource Autoscaling Overview](/docs/guides/qdrant/autoscaler/overview.md) + +To keep everything isolated, we are going to use a separate namespace called `demo` throughout this tutorial. + +```bash +$ kubectl create ns demo +namespace/demo created +``` + +## Autoscaling of Cluster Database + +Here, we are going to deploy a `Qdrant` cluster using a supported version by `KubeDB` operator. Then we are going to apply `QdrantAutoscaler` to set up autoscaling. + +### Deploy Qdrant Cluster + +In this section, we are going to deploy a Qdrant cluster with version `1.17.0`. Then, in the next section we will set up autoscaling for this database using `QdrantAutoscaler` CRD. Below is the YAML of the `Qdrant` CR that we are going to create: + +```yaml +apiVersion: kubedb.com/v1alpha2 +kind: Qdrant +metadata: + name: qdrant-cluster + namespace: demo +spec: + version: "1.17.0" + replicas: 3 + storageType: Durable + storage: + storageClassName: "standard" + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 1Gi + podTemplate: + spec: + containers: + - name: qdrant + resources: + requests: + cpu: "200m" + memory: "512Mi" + limits: + cpu: "200m" + memory: "512Mi" + deletionPolicy: WipeOut +``` + +Let's create the `Qdrant` CR we have shown above: + +```bash +$ kubectl apply -f https://github.com/kubedb/docs/raw/{{< param "info.version" >}}/docs/examples/qdrant/autoscaler/compute/qdrant-cluster.yaml +qdrant.kubedb.com/qdrant-cluster created +``` + +Now, wait until `qdrant-cluster` has status `Ready`: + +```bash +$ kubectl get qdrant -n demo +NAME VERSION STATUS AGE +qdrant-cluster 1.17.0 Ready 4m +``` + +Let's check the Pod container resources: + +```bash +$ kubectl get pod -n demo qdrant-cluster-0 -o json | jq '.spec.containers[].resources' +{ + "limits": { + "cpu": "200m", + "memory": "512Mi" + }, + "requests": { + "cpu": "200m", + "memory": "512Mi" + } +} +``` + +We are now ready to apply the `QdrantAutoscaler` CRD to set up autoscaling for this database. + +### Compute Resource Autoscaling + +Here, we are going to set up compute resource autoscaling using a `QdrantAutoscaler` Object. + +#### Create QdrantAutoscaler Object + +In order to set up compute resource autoscaling for this database cluster, we have to create a `QdrantAutoscaler` CR with our desired configuration. Below is the YAML of the `QdrantAutoscaler` object that we are going to create: + +```yaml +apiVersion: autoscaling.kubedb.com/v1alpha1 +kind: QdrantAutoscaler +metadata: + name: qdrant-as-compute + namespace: demo +spec: + databaseRef: + name: qdrant-cluster + opsRequestOptions: + timeout: 3m + apply: IfReady + compute: + node: + trigger: "On" + podLifeTimeThreshold: 10m + resourceDiffPercentage: 20 + minAllowed: + cpu: 400m + memory: 400Mi + maxAllowed: + cpu: 1 + memory: 2Gi + controlledResources: ["cpu", "memory"] + containerControlledValues: "RequestsAndLimits" +``` + +Here, + +- `spec.databaseRef.name` specifies that we are performing compute autoscaling on `qdrant-cluster` database. +- `spec.compute.node.trigger` specifies that compute resource autoscaling is enabled for the Qdrant nodes. +- `spec.compute.node.podLifeTimeThreshold` specifies the minimum age of a Pod before the `VerticalPodAutoscaler` can recommend a resource update. +- `spec.compute.node.resourceDiffPercentage` specifies the minimum percentage change needed before applying a new resource recommendation. +- `spec.compute.node.minAllowed` specifies the minimum allowed resources for the Qdrant nodes. +- `spec.compute.node.maxAllowed` specifies the maximum allowed resources for the Qdrant nodes. +- `spec.compute.node.controlledResources` specifies the resource types that will be auto-scaled. +- `spec.compute.node.containerControlledValues` specifies which resource values should be controlled, here both requests and limits. + +Let's create the `QdrantAutoscaler` CR we have shown above: + +```bash +$ kubectl apply -f https://github.com/kubedb/docs/raw/{{< param "info.version" >}}/docs/examples/qdrant/autoscaler/compute/qdrant-as-compute.yaml +qdrantautoscaler.autoscaling.kubedb.com/qdrant-as-compute created +``` + +#### Verify Autoscaler is set up successfully + +Let's check that the `QdrantAutoscaler` resource is created successfully: + +```bash +$ kubectl get qdrantautoscaler -n demo +NAME AGE +qdrant-as-compute 5s + +$ kubectl describe qdrantautoscaler qdrant-as-compute -n demo +Name: qdrant-as-compute +Namespace: demo +Labels: +Annotations: +API Version: autoscaling.kubedb.com/v1alpha1 +Kind: QdrantAutoscaler +Spec: + Compute: + Node: + Container Controlled Values: RequestsAndLimits + Controlled Resources: + cpu + memory + Max Allowed: + Cpu: 1 + Memory: 2Gi + Min Allowed: + Cpu: 400m + Memory: 400Mi + Pod Life Time Threshold: 10m0s + Resource Diff Percentage: 20 + Trigger: On + Database Ref: + Name: qdrant-cluster + Ops Request Options: + Apply: IfReady + Timeout: 3m0s +Events: +``` + +So, the `QdrantAutoscaler` resource is created successfully. The operator will now watch the resource usage of the Qdrant pods and create `QdrantOpsRequest` resources to scale the cluster when needed. + +After some time, you can observe that the autoscaler has created a `QdrantOpsRequest` with type `VerticalScaling`: + +```bash +$ kubectl get qdrantopsrequest -n demo +NAME TYPE STATUS AGE +qdops-qdrant-cluster-xxxxxxxx VerticalScaling Successful 5m +``` + +You can then verify the updated resources on the pods: + +```bash +$ kubectl get pod -n demo qdrant-cluster-0 -o json | jq '.spec.containers[].resources' +{ + "limits": { + "cpu": "400m", + "memory": "512Mi" + }, + "requests": { + "cpu": "400m", + "memory": "512Mi" + } +} +``` + +The above output verifies that we have successfully autoscaled the resources of the Qdrant cluster database. + +## Cleaning Up + +To clean up the Kubernetes resources created by this tutorial, run: + +```bash +kubectl delete qdrant -n demo qdrant-cluster +kubectl delete qdrantautoscaler -n demo qdrant-as-compute +kubectl delete ns demo +``` \ No newline at end of file diff --git a/docs/guides/qdrant/autoscaler/overview.md b/docs/guides/qdrant/autoscaler/overview.md index 385a6621b..a9efda082 100644 --- a/docs/guides/qdrant/autoscaler/overview.md +++ b/docs/guides/qdrant/autoscaler/overview.md @@ -10,27 +10,40 @@ menu_name: docs_{{ .version }} section_menu_id: guides --- -# Qdrant Autoscaler +> New to KubeDB? Please start [here](/docs/README.md). -This guide summarizes the autoscaling documentation status for Qdrant. +# Qdrant Autoscaling Overview + +This guide will give an overview of how KubeDB autoscales `Qdrant` database resources — both compute (CPU and memory) and storage. ## Before You Begin -- Deploy Qdrant first using the [quickstart guide](/docs/guides/qdrant/quickstart/quickstart.md). -- Verify feature availability in your installed KubeDB release before applying any autoscaler examples. +- You should be familiar with the following `KubeDB` concepts: + - [Qdrant](/docs/guides/qdrant/concepts/qdrant.md) + - [QdrantAutoscaler](/docs/guides/qdrant/concepts/autoscaler.md) + - [QdrantOpsRequest](/docs/guides/qdrant/concepts/opsrequest.md) + +## How Autoscaling Works + +KubeDB uses the `QdrantAutoscaler` CR to configure automatic scaling of Qdrant resources. There are two types of autoscaling supported: -## Available Autoscaling Modes +### Compute Autoscaling -This repository does not currently contain a `QdrantAutoscaler` Go type or CRD. +KubeDB leverages the [Kubernetes Vertical Pod Autoscaler (VPA)](https://github.com/kubernetes/autoscaler/tree/master/vertical-pod-autoscaler) to recommend compute resource adjustments. The process works as follows: -The compute and storage autoscaler pages are retained as placeholders so the guide tree is complete, but they do not represent CRD-validated manifests in the current repo. +1. The user creates a `QdrantAutoscaler` CR with `spec.compute` configured. +2. KubeDB creates a VPA resource for the `Qdrant` StatefulSet. +3. The VPA monitors resource usage and provides recommendations. +4. When the recommendation differs from the current resources by more than `resourceDiffPercentage`, KubeDB creates a `QdrantOpsRequest` with `type: VerticalScaling` to apply the recommended resources. +5. After the OpsRequest completes, the pods are running with the updated resource requests and limits. -## Related Guides +### Storage Autoscaling -- [Compute Autoscaler](/docs/guides/qdrant/autoscaler/compute/overview.md) -- [Storage Autoscaler](/docs/guides/qdrant/autoscaler/storage/overview.md) +KubeDB monitors PVC usage to automatically expand storage. The process works as follows: -## Next Steps +1. The user creates a `QdrantAutoscaler` CR with `spec.storage` configured. +2. KubeDB monitors the PVC storage usage of the Qdrant pods. +3. When the disk usage exceeds the `usageThreshold` percentage, KubeDB creates a `QdrantOpsRequest` with `type: VolumeExpansion` to expand the storage by `scalingThreshold` percent. +4. After the OpsRequest completes, the PVCs are expanded to the new size. -- Start with conservative limits and thresholds. -- Review autoscaler recommendations before enabling broad production rollouts. +In the next docs, we are going to show step-by-step guides on compute and storage autoscaling for Qdrant databases. diff --git a/docs/guides/qdrant/autoscaler/storage/cluster.md b/docs/guides/qdrant/autoscaler/storage/cluster.md index a90f8add1..f2321171a 100644 --- a/docs/guides/qdrant/autoscaler/storage/cluster.md +++ b/docs/guides/qdrant/autoscaler/storage/cluster.md @@ -10,8 +10,220 @@ menu_name: docs_{{ .version }} section_menu_id: guides --- -# Qdrant Storage Autoscaler +> New to KubeDB? Please start [here](/docs/README.md). -This repository does not currently contain a `QdrantAutoscaler` Go type or CRD. +# Storage Autoscaling of a Qdrant Cluster -Because of that, there is no repository-backed storage autoscaler manifest to publish for Qdrant at this time. \ No newline at end of file +This guide will show you how to use `KubeDB` to autoscale the storage of a Qdrant cluster database. + +## Before You Begin + +- At first, you need to have a Kubernetes cluster, and the `kubectl` command-line tool must be configured to communicate with your cluster. If you do not already have a cluster, you can create one by using [kind](https://kind.sigs.k8s.io/docs/user/quick-start/). + +- Install `KubeDB` Community, Enterprise and Autoscaler operator in your cluster following the steps [here](/docs/setup/README.md). + +- Install `Metrics Server` from [here](https://github.com/kubernetes-sigs/metrics-server#installation). + +- Install Prometheus from [here](https://github.com/prometheus-community/helm-charts/tree/main/charts/kube-prometheus-stack). + +- You must have a `StorageClass` that supports volume expansion. + +- You should be familiar with the following `KubeDB` concepts: + - [Qdrant](/docs/guides/qdrant/concepts/qdrant.md) + - [QdrantOpsRequest](/docs/guides/qdrant/concepts/opsrequest.md) + - [Storage Autoscaling Overview](/docs/guides/qdrant/autoscaler/overview.md) + +To keep everything isolated, we are going to use a separate namespace called `demo` throughout this tutorial. + +```bash +$ kubectl create ns demo +namespace/demo created +``` + +## Storage Autoscaling of Cluster Database + +At first, verify that your cluster has a storage class that supports volume expansion: + +```bash +$ kubectl get storageclass +NAME PROVISIONER RECLAIMPOLICY VOLUMEBINDINGMODE ALLOWVOLUMEEXPANSION AGE +standard (default) rancher.io/local-path Delete WaitForFirstConsumer false 79m +topolvm-provisioner topolvm.cybozu.com Delete WaitForFirstConsumer true 78m +``` + +We can see from the output that `topolvm-provisioner` storage class has `ALLOWVOLUMEEXPANSION` set to `true`. We will use it for this tutorial. You can install topolvm from [here](https://github.com/topolvm/topolvm). + +Now, we are going to deploy a `Qdrant` cluster using a supported version by `KubeDB` operator. Then we are going to apply `QdrantAutoscaler` to set up autoscaling. + +### Deploy Qdrant Cluster + +In this section, we are going to deploy a Qdrant cluster database with version `1.17.0`. Then, in the next section we will set up autoscaling for this database using `QdrantAutoscaler` CRD. Below is the YAML of the `Qdrant` CR that we are going to create: + +```yaml +apiVersion: kubedb.com/v1alpha2 +kind: Qdrant +metadata: + name: qdrant-cluster + namespace: demo +spec: + version: "1.17.0" + replicas: 3 + storageType: Durable + storage: + storageClassName: "topolvm-provisioner" + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 1Gi + deletionPolicy: WipeOut +``` + +Let's create the `Qdrant` CR we have shown above: + +```bash +$ kubectl apply -f https://github.com/kubedb/docs/raw/{{< param "info.version" >}}/docs/examples/qdrant/autoscaler/storage/qdrant-cluster.yaml +qdrant.kubedb.com/qdrant-cluster created +``` + +Now, wait until `qdrant-cluster` has status `Ready`: + +```bash +$ kubectl get qdrant -n demo +NAME VERSION STATUS AGE +qdrant-cluster 1.17.0 Ready 3m46s +``` + +Let's check the volume size from the StatefulSet and from the persistent volumes: + +```bash +$ kubectl get sts -n demo qdrant-cluster -o json | jq '.spec.volumeClaimTemplates[].spec.resources.requests.storage' +"1Gi" + +$ kubectl get pv -n demo +NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE +pvc-43266d76-f280-4cca-bd78-d13660a84db9 1Gi RWO Delete Bound demo/data-qdrant-cluster-2 topolvm-provisioner 57s +pvc-4a509b05-774b-42d9-b36d-599c9056af37 1Gi RWO Delete Bound demo/data-qdrant-cluster-0 topolvm-provisioner 58s +pvc-c27eee12-cd86-4410-b39e-b1dd735fc14d 1Gi RWO Delete Bound demo/data-qdrant-cluster-1 topolvm-provisioner 57s +``` + +You can see the StatefulSet has 1GB storage and the capacity of all the persistent volumes is also 1GB. + +We are now ready to apply the `QdrantAutoscaler` CRD to set up storage autoscaling for this database. + +### Storage Autoscaling + +Here, we are going to set up storage autoscaling using a `QdrantAutoscaler` Object. + +#### Create QdrantAutoscaler Object + +In order to set up storage autoscaling for this cluster database, we have to create a `QdrantAutoscaler` CR with our desired configuration. Below is the YAML of the `QdrantAutoscaler` object that we are going to create: + +```yaml +apiVersion: autoscaling.kubedb.com/v1alpha1 +kind: QdrantAutoscaler +metadata: + name: qdrant-as-storage + namespace: demo +spec: + databaseRef: + name: qdrant-cluster + storage: + node: + trigger: "On" + usageThreshold: 20 + scalingThreshold: 20 + expansionMode: "Online" +``` + +Here, + +- `spec.databaseRef.name` specifies that we are performing storage autoscaling on `qdrant-cluster` database. +- `spec.storage.node.trigger` specifies that storage autoscaling is enabled for the Qdrant nodes. +- `spec.storage.node.usageThreshold` specifies the storage usage threshold — if storage usage exceeds `20%`, storage autoscaling will be triggered. +- `spec.storage.node.scalingThreshold` specifies the scaling threshold — storage will be scaled to `20%` of the current amount. +- `spec.storage.node.expansionMode` specifies the expansion mode of the volume expansion `QdrantOpsRequest` created by `QdrantAutoscaler`. topolvm-provisioner supports online volume expansion so `expansionMode` is set to `Online`. + +Let's create the `QdrantAutoscaler` CR we have shown above: + +```bash +$ kubectl apply -f https://github.com/kubedb/docs/raw/{{< param "info.version" >}}/docs/examples/qdrant/autoscaler/storage/qdrant-as-storage.yaml +qdrantautoscaler.autoscaling.kubedb.com/qdrant-as-storage created +``` + +#### Verify Autoscaler is set up successfully + +Let's check that the `QdrantAutoscaler` resource is created successfully: + +```bash +$ kubectl get qdrantautoscaler -n demo +NAME AGE +qdrant-as-storage 33s + +$ kubectl describe qdrantautoscaler qdrant-as-storage -n demo +Name: qdrant-as-storage +Namespace: demo +Labels: +Annotations: +API Version: autoscaling.kubedb.com/v1alpha1 +Kind: QdrantAutoscaler +Spec: + Database Ref: + Name: qdrant-cluster + Storage: + Node: + Expansion Mode: Online + Scaling Threshold: 20 + Trigger: On + Usage Threshold: 20 +Events: +``` + +So, the `QdrantAutoscaler` resource is created successfully. The operator will now continuously watch the storage usage of the Qdrant pods. When the usage crosses the `usageThreshold`, it will create a `QdrantOpsRequest` to expand the storage. + +Now, for this demo, we are going to manually fill up the persistent volume to exceed the `usageThreshold` using the `dd` command to see if storage autoscaling is working: + +```bash +$ kubectl exec -it -n demo qdrant-cluster-0 -- bash +root@qdrant-cluster-0:/qdrant/storage# df -h /qdrant/storage +Filesystem Size Used Avail Use% Mounted on +/dev/topolvm/57cd4330-784f-42c1-bf8e-e743241df164 1014M 32M 983M 4% /qdrant/storage +root@qdrant-cluster-0:/qdrant/storage# dd if=/dev/zero of=/qdrant/storage/file.img bs=800M count=1 +1+0 records in +1+0 records out +838860800 bytes (839 MB, 800 MiB) copied, 6.47 s, 130 MB/s +root@qdrant-cluster-0:/qdrant/storage# df -h /qdrant/storage +Filesystem Size Used Avail Use% Mounted on +/dev/topolvm/57cd4330-784f-42c1-bf8e-e743241df164 1014M 832M 183M 82% /qdrant/storage +``` + +Now let's watch the `QdrantOpsRequest` in the demo namespace: + +```bash +$ kubectl get qdrantopsrequest -n demo -w +NAME TYPE STATUS AGE +qdops-qdrant-cluster-xxxxxxxx VolumeExpansion Progressing 10s +qdops-qdrant-cluster-xxxxxxxx VolumeExpansion Successful 2m +``` + +After the `QdrantOpsRequest` completes successfully, let's check the updated storage: + +```bash +$ kubectl get pv -n demo +NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE +pvc-43266d76-f280-4cca-bd78-d13660a84db9 1217Mi RWO Delete Bound demo/data-qdrant-cluster-2 topolvm-provisioner 15m +pvc-4a509b05-774b-42d9-b36d-599c9056af37 1217Mi RWO Delete Bound demo/data-qdrant-cluster-0 topolvm-provisioner 15m +pvc-c27eee12-cd86-4410-b39e-b1dd735fc14d 1217Mi RWO Delete Bound demo/data-qdrant-cluster-1 topolvm-provisioner 15m +``` + +The storage has been automatically scaled from 1Gi to ~1.2Gi (120% of 1Gi) as we specified a `scalingThreshold` of 20%. + +## Cleaning Up + +To clean up the Kubernetes resources created by this tutorial, run: + +```bash +kubectl delete qdrant -n demo qdrant-cluster +kubectl delete qdrantautoscaler -n demo qdrant-as-storage +kubectl delete ns demo +``` \ No newline at end of file diff --git a/docs/guides/qdrant/backup/overview.md b/docs/guides/qdrant/backup/overview.md index fc40109b0..61dac7201 100644 --- a/docs/guides/qdrant/backup/overview.md +++ b/docs/guides/qdrant/backup/overview.md @@ -10,66 +10,49 @@ menu_name: docs_{{ .version }} section_menu_id: guides --- -# Qdrant Backup +> New to KubeDB? Please start [here](/docs/README.md). -This guide summarizes backup approaches for Qdrant and follows the same step-by-step flow as the operations guides. +# Qdrant Backup Overview -## Before You Begin - -- Install KubeDB, KubeStash, and a CSI snapshot-capable storage plugin if you plan to use volume snapshots. -- Deploy a Qdrant database in namespace `demo`. -- A single example file is not included under `docs/examples/qdrant/backup` because the exact `BackupConfiguration`, storage backend, and snapshot resources depend on your environment. +This guide will give an overview of how KubeDB supports backup and restore for `Qdrant` databases using [KubeStash](https://kubestash.com). -```bash -kubectl create ns demo -``` - -## Deploy Qdrant +## Before You Begin -```bash -kubectl apply -f https://github.com/kubedb/docs/raw/{{< param "info.version" >}}/docs/examples/qdrant/quickstart/distributed.yaml -kubectl get qdrant -n demo qdrant-sample -w -``` +- You should be familiar with the following `KubeDB` concepts: + - [Qdrant](/docs/guides/qdrant/concepts/qdrant.md) +- You should be familiar with the following `KubeStash` concepts: + - [BackupStorage](https://kubestash.com/docs/latest/concepts/crds/backupstorage/) + - [BackupConfiguration](https://kubestash.com/docs/latest/concepts/crds/backupconfiguration/) + - [BackupSession](https://kubestash.com/docs/latest/concepts/crds/backupsession/) + - [RestoreSession](https://kubestash.com/docs/latest/concepts/crds/restoresession/) + - [RetentionPolicy](https://kubestash.com/docs/latest/concepts/crds/retentionpolicy/) -## Apply Backup Resources +## How Backup Works -## Logical Backup (Restic Plugin) +KubeStash uses a sidecar-based approach to backup Qdrant databases. The backup process consists of the following steps: -Use KubeStash with restic backend for object-level backup workflows. +1. At first, a user creates a `BackupStorage` CR that defines the backend storage location (e.g., S3, GCS, Azure Blob). -Typical resources: +2. Then, the user creates a `RetentionPolicy` CR that defines how long backup snapshots will be retained. -- `BackupStorage` -- `RetentionPolicy` -- `BackupConfiguration` -- `RestoreSession` +3. Then, the user creates a `BackupConfiguration` CR that references the target `Qdrant` database, the `BackupStorage`, and the `RetentionPolicy`. A backup schedule (cron expression) can be defined. -Create backend-specific backup resources that point to your Qdrant database and storage backend. +4. When a `BackupConfiguration` CR is created, KubeStash creates a `CronJob` to trigger backup sessions at the scheduled time. -## Volume Snapshot Backup +5. On each scheduled time, a `BackupSession` CR is created. KubeStash executes the backup in a temporary job that connects to the Qdrant database and writes a snapshot to the backend storage. -Use CSI snapshot compatible storage with snapshot classes for PVC level backup and restore. +6. The backup snapshot is stored in the backend storage and a `Snapshot` CR is created to track the backup metadata. -Typical resources: +## How Restore Works -- `VolumeSnapshotClass` -- `BackupConfiguration` -- `RestoreSession` +The restore process consists of the following steps: -Use a snapshot-capable `StorageClass` and matching `VolumeSnapshotClass` for the PVCs created by your Qdrant deployment. +1. At first, the user creates a target `Qdrant` database (or uses an existing one). -## Verify +2. Then, the user creates a `RestoreSession` CR referencing the `Snapshot` to restore and the target `Qdrant` database. -```bash -kubectl get backupconfiguration -n demo -kubectl get restoresession -n demo -``` +3. KubeStash executes the restore in a temporary job that reads the snapshot from the backend storage and restores the data to the target Qdrant database. -## Cleaning up +4. After the restore completes, the `RestoreSession` status transitions to `Succeeded`. -```bash -kubectl delete backupconfiguration -n demo --all -kubectl delete restoresession -n demo --all -kubectl delete qdrant -n demo qdrant-sample -kubectl delete ns demo -``` +In the next docs, we are going to show step-by-step guides on backup and restore of Qdrant databases using KubeStash. diff --git a/docs/guides/qdrant/concepts/autoscaler.md b/docs/guides/qdrant/concepts/autoscaler.md index 8923f31a4..9b8d495b3 100644 --- a/docs/guides/qdrant/concepts/autoscaler.md +++ b/docs/guides/qdrant/concepts/autoscaler.md @@ -16,11 +16,13 @@ section_menu_id: guides ## What is QdrantAutoscaler -`QdrantAutoscaler` is documented here as a planned resource, but this repository does not currently contain the matching Go type or CRD. +`QdrantAutoscaler` is a Kubernetes `Custom Resource Definitions` (CRD). It provides a declarative configuration for automatic scaling of [Qdrant](https://qdrant.tech/) compute resources (CPU, memory) and storage in a Kubernetes native way. -As a result, the sample shown below is illustrative only and should not be treated as a repository-backed manifest for the current release. +## QdrantAutoscaler CRD Specifications -## Sample QdrantAutoscaler +Like any official Kubernetes resource, a `QdrantAutoscaler` has `TypeMeta`, `ObjectMeta`, `Spec` and `Status` sections. + +**Sample `QdrantAutoscaler` for compute autoscaling:** ```yaml apiVersion: autoscaling.kubedb.com/v1alpha1 @@ -31,25 +33,82 @@ metadata: spec: databaseRef: name: qdrant-sample + opsRequestOptions: + timeout: 3m + apply: IfReady compute: node: trigger: "On" + podLifeTimeThreshold: 10m + resourceDiffPercentage: 20 minAllowed: - cpu: 250m - memory: 512Mi + cpu: 400m + memory: 400Mi maxAllowed: - cpu: "2" - memory: 4Gi + cpu: 1 + memory: 2Gi + controlledResources: ["cpu", "memory"] + containerControlledValues: "RequestsAndLimits" +``` + +**Sample `QdrantAutoscaler` for storage autoscaling:** + +```yaml +apiVersion: autoscaling.kubedb.com/v1alpha1 +kind: QdrantAutoscaler +metadata: + name: qdrant-as-storage + namespace: demo +spec: + databaseRef: + name: qdrant-sample + storage: + node: + trigger: "On" + usageThreshold: 20 + scalingThreshold: 20 + expansionMode: "Online" ``` -## Key fields +### QdrantAutoscaler `Spec` + +A `QdrantAutoscaler` object has the following fields in the `spec` section: + +#### spec.databaseRef + +`spec.databaseRef` is a required field that points to the [Qdrant](/docs/guides/qdrant/concepts/qdrant.md) object for which autoscaling will be performed. It contains: + +- `spec.databaseRef.name` — the name of the target Qdrant database (required). + +#### spec.compute + +`spec.compute` specifies the compute (CPU and memory) autoscaling configuration. It contains a `node` sub-section with the following fields: + +- `spec.compute.node.trigger` — enables (`On`) or disables (`Off`) compute autoscaling. +- `spec.compute.node.podLifeTimeThreshold` — the minimum age of a pod before VPA can recommend resource updates. +- `spec.compute.node.resourceDiffPercentage` — the minimum percentage difference required before applying a recommendation. +- `spec.compute.node.minAllowed` — the minimum allowed CPU and memory resources. +- `spec.compute.node.maxAllowed` — the maximum allowed CPU and memory resources. +- `spec.compute.node.controlledResources` — the list of resources to be controlled (e.g., `["cpu", "memory"]`). +- `spec.compute.node.containerControlledValues` — specifies whether to control `RequestsAndLimits` or `RequestsOnly`. + +#### spec.storage + +`spec.storage` specifies the storage autoscaling configuration. It contains a `node` sub-section with the following fields: + +- `spec.storage.node.trigger` — enables (`On`) or disables (`Off`) storage autoscaling. +- `spec.storage.node.usageThreshold` — the storage usage threshold (percentage) that triggers autoscaling. +- `spec.storage.node.scalingThreshold` — the percentage by which storage will be scaled when triggered. +- `spec.storage.node.expansionMode` — the volume expansion mode (`Online` or `Offline`). + +#### spec.opsRequestOptions + +`spec.opsRequestOptions` specifies the options for the `QdrantOpsRequest` created by the autoscaler. It contains: -- `spec.databaseRef.name` points to the target `Qdrant` database. -- `spec.compute` controls CPU and memory autoscaling behavior. -- `spec.storage` controls volume expansion thresholds and bounds. -- `spec.opsRequestOptions` configures generated ops request behavior. +- `spec.opsRequestOptions.timeout` — the timeout for the generated ops request. +- `spec.opsRequestOptions.apply` — when to apply the ops request. Can be `Always` or `IfReady`. ## Next Steps -- Read [Qdrant autoscaler overview](/docs/guides/qdrant/autoscaler/overview.md). -- See [compute autoscaler guide](/docs/guides/qdrant/autoscaler/compute/overview.md) and [storage autoscaler guide](/docs/guides/qdrant/autoscaler/storage/overview.md). \ No newline at end of file +- Read the [Qdrant autoscaler overview](/docs/guides/qdrant/autoscaler/overview.md). +- See the [compute autoscaler guide](/docs/guides/qdrant/autoscaler/compute/cluster.md) and [storage autoscaler guide](/docs/guides/qdrant/autoscaler/storage/cluster.md). \ No newline at end of file diff --git a/docs/guides/qdrant/concepts/catalog.md b/docs/guides/qdrant/concepts/catalog.md index 49318243f..b4cd9cde3 100644 --- a/docs/guides/qdrant/concepts/catalog.md +++ b/docs/guides/qdrant/concepts/catalog.md @@ -16,12 +16,16 @@ section_menu_id: guides ## What is QdrantVersion -`QdrantVersion` is the catalog CRD that defines image and release metadata for KubeDB-managed Qdrant clusters. +`QdrantVersion` is a Kubernetes `Custom Resource Definitions` (CRD). It provides a declarative configuration to specify the Docker images to be used for [Qdrant](https://qdrant.tech/) database deployed with KubeDB in a Kubernetes native way. -KubeDB resolves `Qdrant.spec.version` using this catalog entry. +When you install KubeDB, a `QdrantVersion` custom resource will be created automatically for every supported Qdrant version. You have to specify the name of the `QdrantVersion` CRD in `spec.version` field of the [Qdrant](/docs/guides/qdrant/concepts/qdrant.md) CRD. Then, KubeDB will use the Docker images specified in the `QdrantVersion` CRD to create your expected database. + +Using a separate CRD for specifying respective Docker images allows us to modify images independent of the KubeDB operator. This also allows users to use a custom image for the database. ## QdrantVersion Specification +As with all other Kubernetes objects, a `QdrantVersion` needs `apiVersion`, `kind`, and `metadata` fields. It also needs a `.spec` section. + ```yaml apiVersion: catalog.kubedb.com/v1alpha1 kind: QdrantVersion @@ -30,18 +34,40 @@ metadata: spec: version: "1.17.0" db: - image: "kubedb/qdrant:1.17.0" + image: "qdrant/qdrant:v1.17.0" deprecated: false ``` -## Key fields +### metadata.name + +`metadata.name` is a required field that specifies the name of the `QdrantVersion` CRD. You have to specify this name in `spec.version` field of the [Qdrant](/docs/guides/qdrant/concepts/qdrant.md) CRD. + +The naming convention for `QdrantVersion` CRD follows the pattern `{Original Qdrant version}`. + +### spec.version + +`spec.version` is a required field that specifies the original version of the Qdrant database that has been used to build the Docker image specified in `spec.db.image` field. -- `metadata.name` is referenced by `Qdrant.spec.version`. -- `spec.version` identifies the engine release. -- `spec.db.image` is the image used by Qdrant pods. -- `spec.deprecated` marks unsupported or legacy versions. +### spec.deprecated + +`spec.deprecated` is an optional field that specifies whether the Docker images specified here are supported by the current KubeDB operator. + +The default value of this field is `false`. If `spec.deprecated` is set to `true`, KubeDB operator will not create the database and other respective resources for this version. + +### spec.db.image + +`spec.db.image` is a required field that specifies the Docker image which will be used to create the StatefulSet by KubeDB operator to create the expected Qdrant database. + +```bash +$ kubectl get qdrantversions +NAME VERSION DB_IMAGE DEPRECATED AGE +1.7.4 1.7.4 qdrant/qdrant:v1.7.4 3d +1.10.0 1.10.0 qdrant/qdrant:v1.10.0 3d +1.14.0 1.14.0 qdrant/qdrant:v1.14.0 3d +1.17.0 1.17.0 qdrant/qdrant:v1.17.0 3d +``` ## Next Steps -- Read the [Qdrant CRD concept](/docs/guides/qdrant/concepts/qdrant.md). -- Run the [Qdrant quickstart](/docs/guides/qdrant/quickstart/quickstart.md). \ No newline at end of file +- Learn about the [Qdrant CRD](/docs/guides/qdrant/concepts/qdrant.md). +- Deploy your first Qdrant database with KubeDB by following the guide [here](/docs/guides/qdrant/quickstart/quickstart.md). \ No newline at end of file diff --git a/docs/guides/qdrant/concepts/opsrequest.md b/docs/guides/qdrant/concepts/opsrequest.md index 867c51da3..4430ac872 100644 --- a/docs/guides/qdrant/concepts/opsrequest.md +++ b/docs/guides/qdrant/concepts/opsrequest.md @@ -16,41 +16,150 @@ section_menu_id: guides ## What is QdrantOpsRequest -`QdrantOpsRequest` is the CRD for day-2 operational workflows for KubeDB-managed Qdrant databases. +`QdrantOpsRequest` is a Kubernetes `Custom Resource Definitions` (CRD). It provides declarative configuration for [Qdrant](https://qdrant.tech/) administrative operations like database version updating, horizontal scaling, vertical scaling, etc. in a Kubernetes native way. -## Supported operation types +## QdrantOpsRequest CRD Specifications -- `Reconfigure` -- `ReconfigureTLS` -- `Restart` -- `RotateAuth` -- `UpdateVersion` -- `HorizontalScaling` -- `VerticalScaling` -- `VolumeExpansion` +Like any official Kubernetes resource, a `QdrantOpsRequest` has `TypeMeta`, `ObjectMeta`, `Spec` and `Status` sections. -## Sample QdrantOpsRequest +Here are some sample `QdrantOpsRequest` CRs for different administrative operations: + +**Sample `QdrantOpsRequest` for updating database version:** + +```yaml +apiVersion: ops.kubedb.com/v1alpha1 +kind: QdrantOpsRequest +metadata: + name: qdops-update-version + namespace: demo +spec: + type: UpdateVersion + databaseRef: + name: qdrant-sample + updateVersion: + targetVersion: "1.18.0" +status: + conditions: + - lastTransitionTime: "2024-10-01T10:00:00Z" + message: The controller has updated the Qdrant successfully + reason: OpsRequestSuccessful + status: "True" + type: Successful + phase: Successful +``` + +**Sample `QdrantOpsRequest` for horizontal scaling:** + +```yaml +apiVersion: ops.kubedb.com/v1alpha1 +kind: QdrantOpsRequest +metadata: + name: qdops-hscale-up + namespace: demo +spec: + type: HorizontalScaling + databaseRef: + name: qdrant-sample + horizontalScaling: + node: 5 +status: + conditions: + - lastTransitionTime: "2024-10-01T10:00:00Z" + message: The controller has scaled/updated the Qdrant successfully + reason: OpsRequestSuccessful + status: "True" + type: Successful + phase: Successful +``` + +**Sample `QdrantOpsRequest` for vertical scaling:** ```yaml apiVersion: ops.kubedb.com/v1alpha1 kind: QdrantOpsRequest metadata: - name: qdrant-restart + name: qdops-vscale namespace: demo spec: - type: Restart + type: VerticalScaling databaseRef: name: qdrant-sample + verticalScaling: + node: + resources: + requests: + memory: "1Gi" + cpu: "500m" + limits: + memory: "1Gi" + cpu: "500m" +status: + conditions: + - lastTransitionTime: "2024-10-01T10:00:00Z" + message: The controller has scaled/updated the Qdrant successfully + reason: OpsRequestSuccessful + status: "True" + type: Successful + phase: Successful ``` -## Key fields +### QdrantOpsRequest `Spec` + +A `QdrantOpsRequest` object has the following fields in the `spec` section: + +#### spec.databaseRef + +`spec.databaseRef` is a required field that points to the [Qdrant](/docs/guides/qdrant/concepts/qdrant.md) object where the administrative operations will be applied. It contains: + +- `spec.databaseRef.name` — the name of the target Qdrant database (required). + +#### spec.type + +`spec.type` specifies the type of operation that will be applied to the database. Supported operations are: + +- `Reconfigure` — reconfigure a running Qdrant database with new configuration. +- `ReconfigureTLS` — reconfigure TLS configuration for a running Qdrant database. +- `Restart` — restart the database pods in a rolling fashion. +- `RotateAuth` — rotate the authentication credentials of a running Qdrant database. +- `UpdateVersion` — update the version of a running Qdrant database. +- `HorizontalScaling` — scale the number of nodes up or down. +- `VerticalScaling` — vertically scale the resources (CPU and memory) of database pods. +- `VolumeExpansion` — expand the persistent volume claim size of a running Qdrant database. + +#### spec.updateVersion + +`spec.updateVersion` is used when `spec.type` is `UpdateVersion`. It contains: + +- `spec.updateVersion.targetVersion` — the target `QdrantVersion` to update to. + +#### spec.horizontalScaling + +`spec.horizontalScaling` is used when `spec.type` is `HorizontalScaling`. It contains: + +- `spec.horizontalScaling.node` — the desired number of Qdrant nodes. + +#### spec.verticalScaling + +`spec.verticalScaling` is used when `spec.type` is `VerticalScaling`. It contains: + +- `spec.verticalScaling.node.resources` — the CPU and memory resource requests and limits for Qdrant nodes. + +#### spec.volumeExpansion + +`spec.volumeExpansion` is used when `spec.type` is `VolumeExpansion`. It contains: + +- `spec.volumeExpansion.node` — the new desired storage size for Qdrant nodes. +- `spec.volumeExpansion.mode` — the volume expansion mode. Can be `Online` or `Offline`. + +#### spec.timeout + +`spec.timeout` is an optional field that specifies the timeout duration for the OpsRequest to complete. If the OpsRequest does not complete within the specified timeout, it will be marked as failed. The value is in the form of a Kubernetes duration (e.g., `5m`, `1h`). + +#### spec.apply -- `spec.type` selects the operation category. -- `spec.databaseRef.name` identifies the target `Qdrant` object. -- Operation-specific fields are provided under keys like `updateVersion`, `horizontalScaling`, `verticalScaling`, or `volumeExpansion`. -- `spec.timeout` and `spec.apply` can control execution behavior where supported. +`spec.apply` is an optional field that specifies when the OpsRequest will be applied. Possible values are `Always` and `IfReady`. The default is `IfReady`, which means the OpsRequest will only be applied when the target database is in `Ready` state. ## Next Steps -- See [Qdrant ops overview](/docs/guides/qdrant/ops-request/overview.md) for operation links. -- Follow operation tutorials like [Restart](/docs/guides/qdrant/restart/restart.md) and [VolumeExpansion](/docs/guides/qdrant/volume-expansion/overview.md). \ No newline at end of file +- See [Qdrant ops request overview](/docs/guides/qdrant/ops-request/overview.md) for operation links. +- Follow operation tutorials like [Restart](/docs/guides/qdrant/restart/restart.md) and [Volume Expansion](/docs/guides/qdrant/volume-expansion/volume-expansion.md). \ No newline at end of file diff --git a/docs/guides/qdrant/concepts/qdrant.md b/docs/guides/qdrant/concepts/qdrant.md index 523e53ef1..7767290f2 100644 --- a/docs/guides/qdrant/concepts/qdrant.md +++ b/docs/guides/qdrant/concepts/qdrant.md @@ -16,10 +16,14 @@ section_menu_id: guides ## What is Qdrant -`Qdrant` is a KubeDB CRD for managing Qdrant vector databases with Kubernetes-native APIs. +`Qdrant` is a Kubernetes `Custom Resource Definitions` (CRD). It provides declarative configuration for [Qdrant](https://qdrant.tech/) vector databases in a Kubernetes native way. You only need to describe the desired database configuration in a `Qdrant` object, and the KubeDB operator will create Kubernetes objects in the desired state for you. ## Qdrant Spec +As with all other Kubernetes objects, a `Qdrant` CR needs `apiVersion`, `kind`, and `metadata` fields. It also needs a `.spec` section. + +Below is an example `Qdrant` object: + ```yaml apiVersion: kubedb.com/v1alpha2 kind: Qdrant @@ -27,25 +31,134 @@ metadata: name: qdrant-sample namespace: demo spec: - version: 1.17.0 - mode: Distributed + version: "1.17.0" replicas: 3 + authSecret: + name: qdrant-sample-auth + configSecret: + name: qdrant-config + storageType: Durable storage: + storageClassName: standard accessModes: - - ReadWriteOnce + - ReadWriteOnce resources: requests: - storage: 2Gi - deletionPolicy: WipeOut + storage: 1Gi + tls: + issuerRef: + apiGroup: "cert-manager.io" + kind: Issuer + name: qdrant-issuer + monitor: + agent: prometheus.io/operator + prometheus: + serviceMonitor: + labels: + app: kubedb + interval: 10s + podTemplate: + metadata: + annotations: + passMe: ToDatabasePod + spec: + serviceAccountName: my-custom-sa + nodeSelector: + disktype: ssd + imagePullSecrets: + - name: myregistrykey + containers: + - name: qdrant + resources: + requests: + memory: "512Mi" + cpu: "500m" + limits: + memory: "1Gi" + cpu: "1" + serviceTemplates: + - alias: primary + spec: + type: LoadBalancer + deletionPolicy: Halt ``` -### Key fields +### spec.version + +`spec.version` is a required field that specifies the name of the [QdrantVersion](/docs/guides/qdrant/concepts/catalog.md) CRD where the docker images are specified. Currently, when you install KubeDB, it creates the following `QdrantVersion` CRDs: + +```bash +$ kubectl get qdrantversions +NAME VERSION DB_IMAGE DEPRECATED AGE +1.7.4 1.7.4 qdrant/qdrant:v1.7.4 3d +1.10.0 1.10.0 qdrant/qdrant:v1.10.0 3d +1.14.0 1.14.0 qdrant/qdrant:v1.14.0 3d +1.17.0 1.17.0 qdrant/qdrant:v1.17.0 3d +``` + +### spec.replicas + +`spec.replicas` is an optional field that specifies the number of Qdrant pods to run. For a single-node deployment, set it to `1`. For a multi-node cluster, set it to the desired number of replicas (e.g., `3`). + +### spec.authSecret + +`spec.authSecret` is an optional field that points to a Secret used for Qdrant API key authentication. If not provided, KubeDB will create one automatically. The Secret must contain an `api-key` data field. + +### spec.configSecret + +`spec.configSecret` is an optional field that points to a Secret containing a custom `production.yaml` configuration file for Qdrant. See [Custom Configuration](/docs/guides/qdrant/configuration/using-config-file.md) for details. + +### spec.storageType + +`spec.storageType` specifies the type of storage that will be used for Qdrant. It can be `Durable` or `Ephemeral`. The default value is `Durable`. If `Ephemeral` is used, KubeDB will create Qdrant using `EmptyDir` volume. In this case, you don't have to specify `spec.storage`. This is useful for testing purposes. + +### spec.storage + +`spec.storage` specifies the StorageClass of PVCs that will be dynamically allocated to store data for Qdrant pods. If `spec.storageType: Ephemeral` is not set, this field is required. + +### spec.tls + +`spec.tls` specifies TLS/SSL configurations for Qdrant. It contains the following sub-fields: + +- `spec.tls.issuerRef` points to a cert-manager `Issuer` or `ClusterIssuer` used to issue certificates. +- `spec.tls.certificates` is an optional field that lists additional certificates for the Qdrant server. + +### spec.monitor + +`spec.monitor` specifies the monitoring configuration for Qdrant. It contains the following sub-field: + +- `spec.monitor.agent` specifies the monitoring agent. Valid values are `prometheus.io/builtin` and `prometheus.io/operator`. + +### spec.podTemplate + +KubeDB allows providing a template for database pods through `spec.podTemplate`. KubeDB operator will pass the information provided in `spec.podTemplate` to the StatefulSet created for Qdrant database. Notable sub-fields include: + +- `spec.podTemplate.spec.serviceAccountName` to provide a custom ServiceAccount. +- `spec.podTemplate.spec.imagePullSecrets` to pull images from a private registry. +- `spec.podTemplate.spec.nodeSelector` to schedule pods on specific nodes. +- `spec.podTemplate.spec.containers[].resources` to configure CPU and memory resources. + +### spec.serviceTemplates + +`spec.serviceTemplates` is an optional field that contains a list of the service templates for the Qdrant services. KubeDB allows following service template variables: + +- `spec.serviceTemplates[].alias`: specifies which service template (e.g., `primary`). +- `spec.serviceTemplates[].spec.type`: specifies the service type (e.g., `ClusterIP`, `LoadBalancer`, `NodePort`). + +### spec.deletionPolicy + +`spec.deletionPolicy` gives freedom to the user to control the behavior of KubeDB when a Qdrant object is deleted. Possible values are: + +- `DoNotTerminate`: prevents deletion of the object if admission webhook is enabled. +- `Halt`: deletes the Qdrant object but keeps the underlying resources (PVCs, Secrets) intact. +- `Delete`: deletes the Qdrant object and its PVCs, but not Secrets. +- `WipeOut`: deletes the Qdrant object and all related resources including PVCs and Secrets. + +### spec.disableSecurity + +`spec.disableSecurity` is an optional boolean field that disables API key authentication when set to `true`. The default is `false`. + +## Next Steps -- `spec.version` points to a `QdrantVersion`. -- `spec.mode` supports `Standalone` and `Distributed`. -- `spec.replicas` controls the number of pods. -- `spec.storageType` and `spec.storage` configure persistence. -- `spec.tls` configures client and p2p TLS. -- `spec.authSecret` and `spec.disableSecurity` control authentication. -- `spec.monitor` integrates monitoring. -- `spec.deletionPolicy` controls delete behavior. \ No newline at end of file +- Learn about [QdrantVersion CRD](/docs/guides/qdrant/concepts/catalog.md). +- Deploy your first Qdrant database with KubeDB by following the guide [here](/docs/guides/qdrant/quickstart/quickstart.md). \ No newline at end of file diff --git a/docs/guides/qdrant/configuration/using-config-file.md b/docs/guides/qdrant/configuration/using-config-file.md index 743a319a9..6e496e7a2 100644 --- a/docs/guides/qdrant/configuration/using-config-file.md +++ b/docs/guides/qdrant/configuration/using-config-file.md @@ -14,29 +14,45 @@ section_menu_id: guides # Using Custom Configuration File -KubeDB uses `spec.configuration.secretName` to provide a custom Qdrant configuration. +KubeDB supports providing custom configuration for Qdrant. This tutorial will show you how to use KubeDB to run a Qdrant database with custom configuration. ## Before You Begin -- You need a Kubernetes cluster and the `kubectl` CLI configured for that cluster. -- Install KubeDB operator following [the setup guide](/docs/setup/README.md). +- At first, you need to have a Kubernetes cluster, and the `kubectl` command-line tool must be configured to communicate with your cluster. If you do not already have a cluster, you can create one by using [kind](https://kind.sigs.k8s.io/docs/user/quick-start/). + +- Now, install KubeDB cli on your workstation and KubeDB operator in your cluster following the steps [here](/docs/setup/README.md). + +- To keep things isolated, this tutorial uses a separate namespace called `demo` throughout this tutorial. ```bash $ kubectl create ns demo namespace/demo created ``` -## Create Configuration Secret +> **Note:** YAML files used in this tutorial are stored in [docs/examples/qdrant/configuration](https://github.com/kubedb/docs/tree/{{< param "info.version" >}}/docs/examples/qdrant/configuration) folder in GitHub repository [kubedb/docs](https://github.com/kubedb/docs). + +## Overview + +Qdrant allows configuring the database via a YAML configuration file named `production.yaml`. When the Qdrant Docker image starts, it merges configuration from the default `config.yaml` with any `production.yaml` file present. KubeDB takes advantage of this feature to allow users to provide their custom configuration. To know more about configuring Qdrant, see [here](https://qdrant.tech/documentation/guides/configuration/). + +At first, you have to create a config file named `production.yaml` with your desired configuration. Then create a Secret with this configuration file and provide its name in `spec.configSecret.name`. The operator reads this Secret and mounts it into the Qdrant pods automatically. + +In this tutorial, we will configure `log_level` and `service.max_request_size_mb` via a custom config file. + +## Custom Configuration -Create a `production.yaml` file with your desired runtime settings: +At first, let's create a `production.yaml` file with custom settings: ```yaml log_level: INFO service: - max_request_size_mb: 32 + max_request_size_mb: 64 +storage: + performance: + max_search_threads: 4 ``` -Create a Secret from this file: +Now, create a Secret with this configuration file: ```bash $ kubectl create secret generic -n demo qdrant-config \ @@ -44,7 +60,27 @@ $ kubectl create secret generic -n demo qdrant-config \ secret/qdrant-config created ``` -## Deploy Qdrant with Custom Configuration +Verify the Secret has the configuration file: + +```yaml +$ kubectl get secret -n demo qdrant-config -o yaml +apiVersion: v1 +data: + production.yaml: bG9nX2xldmVsOiBJTkZPCnNlcnZpY2U6CiAgbWF4X3JlcXVlc3Rfc2l6ZV9tYjogNjQK... +kind: Secret +metadata: + name: qdrant-config + namespace: demo +``` + +Now, create the `Qdrant` CR specifying `spec.configSecret.name` field: + +```bash +$ kubectl apply -f https://github.com/kubedb/docs/raw/{{< param "info.version" >}}/docs/examples/qdrant/configuration/qdrant-configuration.yaml +qdrant.kubedb.com/custom-qdrant created +``` + +Below is the YAML for the `Qdrant` CR we just created: ```yaml apiVersion: kubedb.com/v1alpha2 @@ -53,36 +89,52 @@ metadata: name: custom-qdrant namespace: demo spec: - version: 1.17.0 - mode: Distributed + version: "1.17.0" replicas: 3 - configuration: - secretName: qdrant-config - storageType: Durable + configSecret: + name: qdrant-config storage: + storageClassName: "standard" accessModes: - - ReadWriteOnce + - ReadWriteOnce resources: requests: - storage: 2Gi + storage: 1Gi deletionPolicy: WipeOut ``` +Now, wait a few minutes. KubeDB operator will create the necessary PVC, StatefulSet, services, and secrets. If everything goes well, we will see that a pod with the name `custom-qdrant-0` has been created. + +Check that the StatefulSet's pod is running: + ```bash -$ kubectl apply -f qdrant-configuration.yaml -qdrant.kubedb.com/custom-qdrant created +$ kubectl get pod -n demo custom-qdrant-0 +NAME READY STATUS RESTARTS AGE +custom-qdrant-0 1/1 Running 0 2m ``` -## Verify +Now, wait for the `Qdrant` CR to go into `Ready` state: ```bash $ kubectl get qdrant -n demo custom-qdrant -NAME VERSION STATUS AGE -custom-qdrant 1.17.0 Ready 2m +NAME VERSION STATUS AGE +custom-qdrant 1.17.0 Ready 3m ``` +We can check that the Qdrant database is running with our custom configuration by accessing the telemetry endpoint: + +```bash +$ kubectl port-forward -n demo pod/custom-qdrant-0 6333:6333 & +$ curl http://localhost:6333/telemetry | jq '.result.app.max_request_size_mb' +64 +``` + +The output confirms the database is using our custom `max_request_size_mb` value of `64`. + ## Cleaning up +To cleanup the Kubernetes resources created by this tutorial, run: + ```bash kubectl delete qdrant -n demo custom-qdrant kubectl delete secret -n demo qdrant-config diff --git a/docs/guides/qdrant/custom-rbac/using-custom-rbac.md b/docs/guides/qdrant/custom-rbac/using-custom-rbac.md index 0a300d04f..4bba42d43 100644 --- a/docs/guides/qdrant/custom-rbac/using-custom-rbac.md +++ b/docs/guides/qdrant/custom-rbac/using-custom-rbac.md @@ -14,29 +14,61 @@ section_menu_id: guides # Using Custom RBAC Resources -This tutorial shows how to run Qdrant with custom `ServiceAccount`, `Role`, and `RoleBinding` resources. +KubeDB supports finer user control over role-based access permissions provided to a Qdrant instance. This tutorial will show you how to use KubeDB to run a Qdrant instance with custom RBAC resources. ## Before You Begin -- You need a Kubernetes cluster and the `kubectl` CLI configured for that cluster. -- Install KubeDB operator following [the setup guide](/docs/setup/README.md). -- This tutorial uses `demo` namespace. +- At first, you need to have a Kubernetes cluster, and the `kubectl` command-line tool must be configured to communicate with your cluster. If you do not already have a cluster, you can create one by using [kind](https://kind.sigs.k8s.io/docs/user/quick-start/). + +- Now, install KubeDB cli on your workstation and KubeDB operator in your cluster following the steps [here](/docs/setup/README.md). + +- To keep things isolated, this tutorial uses a separate namespace called `demo` throughout this tutorial. ```bash $ kubectl create ns demo namespace/demo created ``` -## Create Custom RBAC Resources +> **Note:** YAML files used in this tutorial are stored in [docs/examples/qdrant/custom-rbac](https://github.com/kubedb/docs/tree/{{< param "info.version" >}}/docs/examples/qdrant/custom-rbac) folder in GitHub repository [kubedb/docs](https://github.com/kubedb/docs). + +## Overview + +KubeDB allows users to provide custom RBAC resources, namely `ServiceAccount`, `Role`, and `RoleBinding` for Qdrant. This is provided via the `spec.podTemplate.spec.serviceAccountName` field in the Qdrant CRD. If this field is left empty, the KubeDB operator will create a ServiceAccount matching the Qdrant name. Role and RoleBinding that provide necessary access permissions will also be generated automatically for this ServiceAccount. + +If a ServiceAccount name is given but there is no existing ServiceAccount by that name, the KubeDB operator will create one, and Role and RoleBinding will also be generated automatically. + +If a ServiceAccount name is given and there is an existing ServiceAccount by that name, the KubeDB operator will use that existing ServiceAccount. Since this ServiceAccount is not managed by KubeDB, users are responsible for providing necessary access permissions manually. + +This guide will show you how to create custom `ServiceAccount`, `Role`, and `RoleBinding` for a Qdrant instance named `qdrant-custom-rbac` to provide the bare minimum access permissions. + +## Custom RBAC for Qdrant -Create the service account first: +At first, let's create a `ServiceAccount` in the `demo` namespace: ```bash $ kubectl create serviceaccount -n demo my-custom-serviceaccount serviceaccount/my-custom-serviceaccount created ``` -Create a role with the required permissions for Qdrant Pods and related resources: +Verify that the ServiceAccount was created: + +```yaml +$ kubectl get serviceaccount -n demo my-custom-serviceaccount -o yaml +apiVersion: v1 +kind: ServiceAccount +metadata: + name: my-custom-serviceaccount + namespace: demo +``` + +Now, create a Role that has the necessary access permissions for the Qdrant database named `qdrant-custom-rbac`: + +```bash +$ kubectl apply -f https://github.com/kubedb/docs/raw/{{< param "info.version" >}}/docs/examples/qdrant/custom-rbac/qdrant-custom-role.yaml +role.rbac.authorization.k8s.io/my-custom-role created +``` + +Below is the YAML for the Role we just created: ```yaml apiVersion: rbac.authorization.k8s.io/v1 @@ -46,25 +78,55 @@ metadata: namespace: demo rules: - apiGroups: - - kubedb.com + - apps + resourceNames: + - qdrant-custom-rbac resources: - - qdrants + - statefulsets + verbs: + - get +- apiGroups: + - kubedb.com resourceNames: - qdrant-custom-rbac + resources: + - qdrants verbs: - get - apiGroups: - "" resources: - pods + verbs: + - list + - patch + - delete +- apiGroups: + - "" + resources: - pods/exec verbs: + - create +- apiGroups: + - "" + resources: + - secrets + verbs: - get - list +- apiGroups: + - "" + resources: + - configmaps + verbs: - create + - get + - update ``` -Bind the role to the service account: +Note that `resourceNames` like `qdrant-custom-rbac` are unique to this particular Qdrant instance. Another database instance `qdrant-custom-rbac-2` would require these `resourceNames` to be updated accordingly. + +Now, create a `RoleBinding` to bind this `Role` with the already created ServiceAccount: ```bash $ kubectl create rolebinding my-custom-rolebinding \ @@ -74,9 +136,33 @@ $ kubectl create rolebinding my-custom-rolebinding \ rolebinding.rbac.authorization.k8s.io/my-custom-rolebinding created ``` -## Deploy Qdrant with Custom Service Account +Verify the RoleBinding was created: + +```yaml +$ kubectl get rolebinding -n demo my-custom-rolebinding -o yaml +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: my-custom-rolebinding + namespace: demo +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: my-custom-role +subjects: +- kind: ServiceAccount + name: my-custom-serviceaccount + namespace: demo +``` + +Now, create a `Qdrant` CR specifying `spec.podTemplate.spec.serviceAccountName` field to `my-custom-serviceaccount`: + +```bash +$ kubectl apply -f https://github.com/kubedb/docs/raw/{{< param "info.version" >}}/docs/examples/qdrant/custom-rbac/qdrant-custom-db.yaml +qdrant.kubedb.com/qdrant-custom-rbac created +``` -## Deploy Qdrant with Custom Service Account +Below is the YAML for the `Qdrant` CR we just created: ```yaml apiVersion: kubedb.com/v1alpha2 @@ -85,39 +171,41 @@ metadata: name: qdrant-custom-rbac namespace: demo spec: - version: 1.17.0 - mode: Distributed + version: "1.17.0" replicas: 3 podTemplate: spec: serviceAccountName: my-custom-serviceaccount - storageType: Durable storage: + storageClassName: "standard" accessModes: - - ReadWriteOnce + - ReadWriteOnce resources: requests: - storage: 2Gi + storage: 1Gi deletionPolicy: WipeOut ``` -```bash -$ kubectl apply -f qdrant-custom-rbac.yaml -qdrant.kubedb.com/qdrant-custom-rbac created -``` - -## Verify +Now, wait a few minutes. If everything goes well, we will see that the Qdrant pods are running with the custom ServiceAccount: ```bash $ kubectl get qdrant -n demo qdrant-custom-rbac -NAME VERSION STATUS AGE -qdrant-custom-rbac 1.17.0 Ready 2m - -$ kubectl get pod -n demo -l app.kubernetes.io/instance=qdrant-custom-rbac +NAME VERSION STATUS AGE +qdrant-custom-rbac 1.17.0 Ready 2m + +$ kubectl get pod -n demo -l app.kubernetes.io/instance=qdrant-custom-rbac -o=custom-columns=NAME:.metadata.name,SERVICEACCOUNT:.spec.serviceAccountName +NAME SERVICEACCOUNT +qdrant-custom-rbac-0 my-custom-serviceaccount +qdrant-custom-rbac-1 my-custom-serviceaccount +qdrant-custom-rbac-2 my-custom-serviceaccount ``` +The output confirms that all Qdrant pods are running with our custom `my-custom-serviceaccount` ServiceAccount. + ## Cleaning up +To clean up the Kubernetes resources created by this tutorial, run: + ```bash kubectl delete qdrant -n demo qdrant-custom-rbac kubectl delete rolebinding -n demo my-custom-rolebinding diff --git a/docs/guides/qdrant/monitoring/overview.md b/docs/guides/qdrant/monitoring/overview.md index fde86dc74..21007e9f1 100644 --- a/docs/guides/qdrant/monitoring/overview.md +++ b/docs/guides/qdrant/monitoring/overview.md @@ -10,30 +10,29 @@ menu_name: docs_{{ .version }} section_menu_id: guides --- -# Qdrant Monitoring +> New to KubeDB? Please start [here](/docs/README.md). -This guide shows how to enable and verify monitoring for Qdrant. +# Qdrant Monitoring Overview + +This guide will give an overview of how KubeDB supports monitoring for `Qdrant` databases. ## Before You Begin -- Deploy Qdrant first using the [quickstart guide](/docs/guides/qdrant/quickstart/quickstart.md). -- Install Prometheus Operator or another compatible metrics stack. +- You should be familiar with the following `KubeDB` concepts: + - [Qdrant](/docs/guides/qdrant/concepts/qdrant.md) -## Enable Monitoring +## How KubeDB Monitoring Works -Qdrant monitoring can be configured through `spec.monitor`. +KubeDB uses Prometheus to monitor `Qdrant` databases. KubeDB operator watches the `Qdrant` CR and sets up monitoring as follows: -- Enable metric scraping and verify health endpoints. -- Track shard status and node health in distributed mode. +1. When a `Qdrant` database is deployed with `spec.monitor` configured, KubeDB creates a dedicated `stats` service (or uses the existing service) with the appropriate annotations for Prometheus scraping. -## Verify +2. KubeDB supports two monitoring approaches: + - **Builtin Prometheus** — uses Prometheus' built-in auto-discovery mechanism (`prometheus.io/scrape` annotations on the stats service). + - **Prometheus Operator** — creates a `ServiceMonitor` CR that is picked up by the Prometheus Operator. -```bash -kubectl get qdrant -n demo qdrant-sample -o yaml -kubectl get servicemonitor -A -``` +3. The Qdrant stats service exposes Prometheus-compatible metrics at the `/metrics` endpoint, including metrics about collections, vectors, memory usage, and gRPC/REST request performance. -## Next Steps +4. Prometheus scrapes the metrics from the stats service and makes them available for alerting and dashboards. -- Add dashboards for shard balance, request latency, and storage growth. -- Recheck metrics after scaling or version updates. +In the next docs, we are going to show step-by-step guides on monitoring a Qdrant database using Builtin Prometheus and Prometheus Operator. diff --git a/docs/guides/qdrant/monitoring/using-builtin-prometheus.md b/docs/guides/qdrant/monitoring/using-builtin-prometheus.md index 3f6a1f02a..9ddfe45ef 100644 --- a/docs/guides/qdrant/monitoring/using-builtin-prometheus.md +++ b/docs/guides/qdrant/monitoring/using-builtin-prometheus.md @@ -14,20 +14,34 @@ section_menu_id: guides # Monitoring Qdrant with Builtin Prometheus -This guide shows how to enable builtin Prometheus scraping for Qdrant. +This tutorial will show you how to monitor `Qdrant` database using builtin [Prometheus](https://github.com/prometheus/prometheus) scraper. ## Before You Begin -- Install KubeDB operator from [setup guide](/docs/setup/README.md). -- Use separate namespaces for database and monitoring resources. +- At first, you need to have a Kubernetes cluster, and the `kubectl` command-line tool must be configured to communicate with your cluster. If you do not already have a cluster, you can create one by using [kind](https://kind.sigs.k8s.io/docs/user/quick-start/). + +- Install `KubeDB` operator in your cluster following the steps [here](/docs/setup/README.md). + +- If you are not familiar with how to configure Prometheus to scrape metrics from various Kubernetes resources, please read the tutorial from [here](https://github.com/appscode/third-party-tools/tree/master/monitoring/prometheus/builtin). + +- To learn how Prometheus monitoring works with KubeDB in general, please visit [here](/docs/guides/qdrant/monitoring/overview.md). + +- To keep Prometheus resources isolated, we are going to use a separate namespace called `monitoring` to deploy respective monitoring resources. We are going to deploy the database in `demo` namespace. ```bash -$ kubectl create ns demo $ kubectl create ns monitoring +namespace/monitoring created + +$ kubectl create ns demo +namespace/demo created ``` +> **Note:** YAML files used in this tutorial are stored in [docs/examples/qdrant/monitoring](https://github.com/kubedb/docs/tree/{{< param "info.version" >}}/docs/examples/qdrant/monitoring) folder in GitHub repository [kubedb/docs](https://github.com/kubedb/docs). + ## Deploy Qdrant with Monitoring Enabled +At first, let's deploy a `Qdrant` database with monitoring enabled. Below is the `Qdrant` object that we are going to create: + ```yaml apiVersion: kubedb.com/v1alpha2 kind: Qdrant @@ -35,37 +49,109 @@ metadata: name: builtin-prom-qdrant namespace: demo spec: - version: 1.17.0 - mode: Distributed + version: "1.17.0" replicas: 3 - storageType: Durable storage: + storageClassName: "standard" accessModes: - - ReadWriteOnce + - ReadWriteOnce resources: requests: - storage: 2Gi + storage: 1Gi monitor: agent: prometheus.io/builtin deletionPolicy: WipeOut ``` +Here, + +- `spec.monitor.agent: prometheus.io/builtin` specifies that we are going to monitor this server using builtin Prometheus scraper. + +Let's create the `Qdrant` CR we have shown above: + ```bash -$ kubectl apply -f builtin-prom-qdrant.yaml +$ kubectl apply -f https://github.com/kubedb/docs/raw/{{< param "info.version" >}}/docs/examples/qdrant/monitoring/builtin-prom-qdrant.yaml qdrant.kubedb.com/builtin-prom-qdrant created ``` -## Verify +Now, wait for the database to go into `Ready` state: ```bash $ kubectl get qdrant -n demo builtin-prom-qdrant -$ kubectl get svc -n demo --selector=app.kubernetes.io/instance=builtin-prom-qdrant +NAME VERSION STATUS AGE +builtin-prom-qdrant 1.17.0 Ready 1m +``` + +KubeDB will create a separate stats service with name `{Qdrant cr name}-stats` for monitoring purpose. + +```bash +$ kubectl get svc -n demo --selector="app.kubernetes.io/instance=builtin-prom-qdrant" +NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE +builtin-prom-qdrant ClusterIP 10.102.7.190 6333/TCP 87s +builtin-prom-qdrant-stats ClusterIP 10.102.128.153 6333/TCP 56s ``` -The operator creates a stats service with scrape annotations for builtin Prometheus discovery. +Here, `builtin-prom-qdrant-stats` service has been created for monitoring purpose. Let's describe the service: + +```bash +$ kubectl describe svc -n demo builtin-prom-qdrant-stats +Name: builtin-prom-qdrant-stats +Namespace: demo +Labels: app.kubernetes.io/component=database + app.kubernetes.io/instance=builtin-prom-qdrant + app.kubernetes.io/managed-by=kubedb.com + app.kubernetes.io/name=qdrants.kubedb.com +Annotations: monitoring.appscode.com/agent: prometheus.io/builtin + prometheus.io/path: /metrics + prometheus.io/port: 6333 + prometheus.io/scrape: true +Selector: app.kubernetes.io/instance=builtin-prom-qdrant,app.kubernetes.io/name=qdrants.kubedb.com +Type: ClusterIP +Port: metrics 6333/TCP +TargetPort: metrics/TCP +Endpoints: 10.244.1.5:6333,10.244.1.6:6333,10.244.1.7:6333 +``` + +You can see that the service contains the following annotations: + +``` +prometheus.io/scrape: true +prometheus.io/path: /metrics +prometheus.io/port: 6333 +``` + +The Prometheus server will discover this service endpoint using these annotations and will scrape metrics from all endpoints. + +## Configure Prometheus to Scrape + +To get the monitoring of this `Qdrant` database, you need to configure a Prometheus server. Below is the necessary configuration: + +```yaml +global: + scrape_interval: 15s + +scrape_configs: +- job_name: 'kubedb-databases' + kubernetes_sd_configs: + - role: endpoints + relabel_configs: + - source_labels: [__meta_kubernetes_service_annotation_prometheus_io_scrape] + regex: true + action: keep + - source_labels: [__meta_kubernetes_service_annotation_prometheus_io_path] + regex: (.+) + target_label: __metrics_path__ + action: replace +``` + +## Verify Monitoring + +Once Prometheus is configured and running, you can check the monitoring is working by navigating to the Prometheus dashboard (by default at `localhost:9090`). You should see the `builtin-prom-qdrant-stats` endpoint in the list of scrape targets. ## Cleaning up +To clean up the Kubernetes resources created by this tutorial, run: + ```bash kubectl delete qdrant -n demo builtin-prom-qdrant kubectl delete ns demo diff --git a/docs/guides/qdrant/monitoring/using-prometheus-operator.md b/docs/guides/qdrant/monitoring/using-prometheus-operator.md index 9935b68d4..d4e50183b 100644 --- a/docs/guides/qdrant/monitoring/using-prometheus-operator.md +++ b/docs/guides/qdrant/monitoring/using-prometheus-operator.md @@ -12,22 +12,72 @@ section_menu_id: guides > New to KubeDB? Please start [here](/docs/README.md). -# Monitoring Qdrant using Prometheus Operator +# Monitoring Qdrant Using Prometheus Operator -This guide shows how to expose Qdrant metrics using Prometheus Operator integration. +[Prometheus operator](https://github.com/prometheus-operator/prometheus-operator) provides a simple and Kubernetes-native way to deploy and configure Prometheus server. This tutorial will show you how to use Prometheus operator to monitor `Qdrant` database deployed with KubeDB. ## Before You Begin -- Install KubeDB operator from [setup guide](/docs/setup/README.md). -- Ensure Prometheus Operator is installed in your cluster. +- At first, you need to have a Kubernetes cluster, and the `kubectl` command-line tool must be configured to communicate with your cluster. If you do not already have a cluster, you can create one by using [kind](https://kind.sigs.k8s.io/docs/user/quick-start/). + +- To learn how Prometheus monitoring works with KubeDB in general, please visit [here](/docs/guides/qdrant/monitoring/overview.md). + +- To keep Prometheus resources isolated, we are going to use a separate namespace called `monitoring` to deploy respective monitoring resources. We are going to deploy the database in `demo` namespace. ```bash -$ kubectl create ns demo $ kubectl create ns monitoring +namespace/monitoring created + +$ kubectl create ns demo +namespace/demo created ``` +- We need a [Prometheus operator](https://github.com/prometheus-operator/prometheus-operator) instance running. If you don't already have a running instance, deploy one following the docs from [here](https://github.com/appscode/third-party-tools/blob/master/monitoring/prometheus/operator/README.md). + +- If you don't already have a Prometheus server running, deploy one following the tutorial from [here](https://github.com/appscode/third-party-tools/blob/master/monitoring/prometheus/operator/README.md#deploy-prometheus-server). + +> **Note:** YAML files used in this tutorial are stored in [docs/examples/qdrant/monitoring](https://github.com/kubedb/docs/tree/{{< param "info.version" >}}/docs/examples/qdrant/monitoring) folder in GitHub repository [kubedb/docs](https://github.com/kubedb/docs). + +## Find out required labels for ServiceMonitor + +We need to know the labels used to select `ServiceMonitor` by a `Prometheus` CR. We are going to provide these labels in `spec.monitor.prometheus.serviceMonitor.labels` field of the `Qdrant` CR so that KubeDB creates `ServiceMonitor` object accordingly. + +At first, let's find out the available Prometheus server in our cluster: + +```bash +$ kubectl get prometheus --all-namespaces +NAMESPACE NAME AGE +monitoring prometheus 18m +``` + +Now, let's view the YAML of the available Prometheus server `prometheus` in `monitoring` namespace: + +```yaml +$ kubectl get prometheus -n monitoring prometheus -o yaml +apiVersion: monitoring.coreos.com/v1 +kind: Prometheus +metadata: + labels: + prometheus: prometheus + name: prometheus + namespace: monitoring +spec: + replicas: 1 + resources: + requests: + memory: 400Mi + serviceAccountName: prometheus + serviceMonitorSelector: + matchLabels: + release: prometheus +``` + +Notice the `spec.serviceMonitorSelector` section. Here, `release: prometheus` label is used to select `ServiceMonitor` CR. So, we are going to use this label in `spec.monitor.prometheus.serviceMonitor.labels` field of the `Qdrant` CR. + ## Deploy Qdrant with Monitoring Enabled +At first, let's deploy a `Qdrant` database with monitoring enabled. Below is the `Qdrant` object that we are going to create: + ```yaml apiVersion: kubedb.com/v1alpha2 kind: Qdrant @@ -35,16 +85,15 @@ metadata: name: coreos-prom-qdrant namespace: demo spec: - version: 1.17.0 - mode: Distributed + version: "1.17.0" replicas: 3 - storageType: Durable storage: + storageClassName: "standard" accessModes: - - ReadWriteOnce + - ReadWriteOnce resources: requests: - storage: 2Gi + storage: 1Gi monitor: agent: prometheus.io/operator prometheus: @@ -55,20 +104,75 @@ spec: deletionPolicy: WipeOut ``` +Here, + +- `spec.monitor.agent: prometheus.io/operator` specifies that we are going to monitor this server using Prometheus operator. +- `spec.monitor.prometheus.serviceMonitor.labels` specifies that KubeDB should create `ServiceMonitor` with these labels. +- `spec.monitor.prometheus.serviceMonitor.interval` specifies how frequently Prometheus should scrape this database. + +Let's create the `Qdrant` CR we have shown above: + ```bash -$ kubectl apply -f coreos-prom-qdrant.yaml +$ kubectl apply -f https://github.com/kubedb/docs/raw/{{< param "info.version" >}}/docs/examples/qdrant/monitoring/coreos-prom-qdrant.yaml qdrant.kubedb.com/coreos-prom-qdrant created ``` -## Verify +Now, wait for the database to go into `Ready` state: ```bash $ kubectl get qdrant -n demo coreos-prom-qdrant +NAME VERSION STATUS AGE +coreos-prom-qdrant 1.17.0 Ready 1m +``` + +KubeDB will create a `ServiceMonitor` object for this `Qdrant` database: + +```bash $ kubectl get servicemonitor -n demo +NAME AGE +coreos-prom-qdrant 65s +``` + +Let's verify the `ServiceMonitor` has the labels we specified: + +```bash +$ kubectl get servicemonitor -n demo coreos-prom-qdrant -o yaml +apiVersion: monitoring.coreos.com/v1 +kind: ServiceMonitor +metadata: + labels: + app.kubernetes.io/component: database + app.kubernetes.io/instance: coreos-prom-qdrant + app.kubernetes.io/managed-by: kubedb.com + app.kubernetes.io/name: qdrants.kubedb.com + release: prometheus + name: coreos-prom-qdrant + namespace: demo +spec: + endpoints: + - honorLabels: true + interval: 10s + path: /metrics + port: metrics + namespaceSelector: + matchNames: + - demo + selector: + matchLabels: + app.kubernetes.io/instance: coreos-prom-qdrant + app.kubernetes.io/name: qdrants.kubedb.com ``` +Notice that the `ServiceMonitor` has the label `release: prometheus`, which will be picked up by the Prometheus server. + +## Verify Monitoring + +Once everything is set up, you can visit the Prometheus dashboard. The `coreos-prom-qdrant` service monitor will be discovered and its metrics will be scraped. You should be able to query Qdrant metrics in Prometheus. + ## Cleaning up +To clean up the Kubernetes resources created by this tutorial, run: + ```bash kubectl delete qdrant -n demo coreos-prom-qdrant kubectl delete ns demo diff --git a/docs/guides/qdrant/ops-request/overview.md b/docs/guides/qdrant/ops-request/overview.md index e2e09057c..62ec04e59 100644 --- a/docs/guides/qdrant/ops-request/overview.md +++ b/docs/guides/qdrant/ops-request/overview.md @@ -10,31 +10,42 @@ menu_name: docs_{{ .version }} section_menu_id: guides --- -# Qdrant Ops Request +> New to KubeDB? Please start [here](/docs/README.md). -This guide lists the Qdrant operations currently documented for KubeDB. +# Qdrant Day-2 Operations + +This guide provides an overview of the day-2 operational workflows that KubeDB supports for `Qdrant` databases via the `QdrantOpsRequest` CRD. ## Before You Begin -- Deploy Qdrant first using the [quickstart guide](/docs/guides/qdrant/quickstart/quickstart.md). -- Review the operation-specific pages before applying changes in production. +- You should be familiar with the following `KubeDB` concepts: + - [Qdrant](/docs/guides/qdrant/concepts/qdrant.md) + - [QdrantOpsRequest](/docs/guides/qdrant/concepts/opsrequest.md) + +## Supported Operations -## Supported Ops Requests +KubeDB supports the following day-2 operations for Qdrant: -- [HorizontalScaling](/docs/guides/qdrant/scaling/horizontal-scaling/overview.md) -- [Reconfigure](/docs/guides/qdrant/reconfigure/overview.md) -- [ReconfigureTLS](/docs/guides/qdrant/reconfigure-tls/overview.md) -- [Restart](/docs/guides/qdrant/restart/restart.md) -- [RotateAuth](/docs/guides/qdrant/rotate-auth/overview.md) -- [VerticalScaling](/docs/guides/qdrant/scaling/vertical-scaling/overview.md) -- [VolumeExpansion](/docs/guides/qdrant/volume-expansion/overview.md) -- [UpdateVersion](/docs/guides/qdrant/update-version/overview.md) +| Operation | Description | +|-----------|-------------| +| [UpdateVersion](/docs/guides/qdrant/update-version/overview.md) | Update the version of a running Qdrant database | +| [HorizontalScaling](/docs/guides/qdrant/scaling/horizontal-scaling/overview.md) | Scale the number of Qdrant nodes up or down | +| [VerticalScaling](/docs/guides/qdrant/scaling/vertical-scaling/overview.md) | Update CPU and memory resources of Qdrant nodes | +| [VolumeExpansion](/docs/guides/qdrant/volume-expansion/overview.md) | Expand the persistent volume claim size of Qdrant nodes | +| [Reconfigure](/docs/guides/qdrant/reconfigure/overview.md) | Reconfigure a running Qdrant database with new configuration | +| [ReconfigureTLS](/docs/guides/qdrant/reconfigure-tls/overview.md) | Add, rotate, or remove TLS certificates for Qdrant | +| [Restart](/docs/guides/qdrant/restart/restart.md) | Restart the Qdrant database pods in a rolling fashion | +| [RotateAuth](/docs/guides/qdrant/rotate-auth/overview.md) | Rotate the authentication credentials of a Qdrant database | ## How Ops Requests Work -Create a `QdrantOpsRequest` for the target database, wait for the request to complete, and verify both the request object and the database status before moving to the next change. +All day-2 operations for Qdrant are performed through the `QdrantOpsRequest` CRD. The general workflow is: -## Next Steps +1. The user creates a `QdrantOpsRequest` CR with the desired operation type and parameters. +2. `KubeDB-ops-manager` operator watches for `QdrantOpsRequest` CRs. +3. When it finds one, it pauses the `Qdrant` object to prevent conflicting operations. +4. The operator performs the requested operation (e.g., updates images, scales nodes, expands volumes). +5. After the operation completes successfully, the operator updates the `Qdrant` object and resumes it. +6. The `QdrantOpsRequest` status transitions to `Successful`. -- Choose the specific operation page that matches your intended change. -- Apply one operation at a time and wait for completion before starting the next. +> **Note:** Only one `QdrantOpsRequest` should be active at a time for a given `Qdrant` database. Wait for one operation to complete before starting another. diff --git a/docs/guides/qdrant/private-registry/using-private-registry.md b/docs/guides/qdrant/private-registry/using-private-registry.md index ba7f32ad5..a2c8b7120 100644 --- a/docs/guides/qdrant/private-registry/using-private-registry.md +++ b/docs/guides/qdrant/private-registry/using-private-registry.md @@ -14,41 +14,88 @@ section_menu_id: guides # Using Private Docker Registry -This tutorial shows how to run KubeDB managed Qdrant using private Docker images. +KubeDB supports using private Docker registry. This tutorial will show you how to run KubeDB managed Qdrant database using private Docker images. ## Before You Begin -- Prepare a Kubernetes cluster and `kubectl`. +- At first, you need to have a Kubernetes cluster, and the `kubectl` command-line tool must be configured to communicate with your cluster. If you do not already have a cluster, you can create one by using [kind](https://kind.sigs.k8s.io/docs/user/quick-start/). + +- To keep things isolated, this tutorial uses a separate namespace called `demo` throughout this tutorial. + +```bash +$ kubectl create ns demo +namespace/demo created +``` + +> **Note:** YAML files used in this tutorial are stored in [docs/examples/qdrant/private-registry](https://github.com/kubedb/docs/tree/{{< param "info.version" >}}/docs/examples/qdrant/private-registry) folder in GitHub repository [kubedb/docs](https://github.com/kubedb/docs). + +## Prepare Private Docker Registry + +You will need a Docker private [registry](https://docs.docker.com/registry/) or [private repository](https://docs.docker.com/docker-hub/repos/#private-repositories). In this tutorial we will use a private repository on [Docker Hub](https://hub.docker.com/). + +You need to push the required images from KubeDB's [Docker hub account](https://hub.docker.com/r/qdrant/) into your private registry. For Qdrant, push `DB_IMAGE` of the following `QdrantVersion`s, where `deprecated` is not true, to your private registry. ```bash -kubectl create ns demo +$ kubectl get qdrantversions -o=custom-columns=NAME:.metadata.name,VERSION:.spec.version,DB_IMAGE:.spec.db.image,DEPRECATED:.spec.deprecated +NAME VERSION DB_IMAGE DEPRECATED +1.7.4 1.7.4 qdrant/qdrant:v1.7.4 +1.10.0 1.10.0 qdrant/qdrant:v1.10.0 +1.14.0 1.14.0 qdrant/qdrant:v1.14.0 +1.17.0 1.17.0 qdrant/qdrant:v1.17.0 ``` +Docker hub repository: +- [qdrant/qdrant](https://hub.docker.com/r/qdrant/qdrant) + ## Create ImagePullSecret +`ImagePullSecrets` is a type of Kubernetes Secret whose purpose is to pull private images from a Docker registry. It allows you to specify the URL of the Docker registry, credentials for logging in, and the image name of your private Docker image. + +Run the following command, substituting the appropriate uppercase values, to create an image pull secret for your private Docker registry: + ```bash -kubectl create secret docker-registry -n demo myregistrykey \ +$ kubectl create secret docker-registry -n demo myregistrykey \ --docker-server=DOCKER_REGISTRY_SERVER \ --docker-username=DOCKER_USER \ --docker-email=DOCKER_EMAIL \ --docker-password=DOCKER_PASSWORD +secret/myregistrykey created ``` +If you wish to follow other ways to pull private images see [official docs](https://kubernetes.io/docs/concepts/containers/images/) of Kubernetes. + +## Install KubeDB operator + +When installing KubeDB operator, set the flags `--docker-registry` and `--image-pull-secret` to the appropriate values. Follow the steps to [install KubeDB operator](/docs/setup/README.md) properly in your cluster so that it points to the `DOCKER_REGISTRY` you wish to pull images from. + ## Create QdrantVersion CRD +KubeDB uses images specified in `QdrantVersion` CRD for the database. You have to create a `QdrantVersion` CRD specifying images from your private registry. Then, you have to point this `QdrantVersion` CRD in `spec.version` field of the `Qdrant` object. For more details about `QdrantVersion` CRD, please visit [here](/docs/guides/qdrant/concepts/catalog.md). + +Here is an example of a `QdrantVersion` CRD. Replace `PRIVATE_REGISTRY` with your private registry: + ```yaml apiVersion: catalog.kubedb.com/v1alpha1 kind: QdrantVersion metadata: - name: "1.17.0" + name: "1.17.0-private" spec: db: - image: PRIVATE_REGISTRY/qdrant:1.17.0 + image: PRIVATE_REGISTRY/qdrant:v1.17.0 version: "1.17.0" ``` +```bash +$ kubectl apply -f https://github.com/kubedb/docs/raw/{{< param "info.version" >}}/docs/examples/qdrant/private-registry/qdrantversion.yaml +qdrantversion.catalog.kubedb.com/1.17.0-private created +``` + ## Deploy Qdrant from Private Registry +While deploying `Qdrant` from private registry, you have to add `myregistrykey` secret in `spec.podTemplate.spec.imagePullSecrets` and specify `1.17.0-private` in `spec.version` field. + +Below is the YAML for Qdrant crd we are going to create: + ```yaml apiVersion: kubedb.com/v1alpha2 kind: Qdrant @@ -56,25 +103,42 @@ metadata: name: pvt-reg-qdrant namespace: demo spec: - version: 1.17.0 - mode: Distributed + version: "1.17.0-private" replicas: 3 - storageType: Durable storage: + storageClassName: "standard" accessModes: - - ReadWriteOnce + - ReadWriteOnce resources: requests: - storage: 2Gi + storage: 1Gi podTemplate: spec: imagePullSecrets: - - name: myregistrykey + - name: myregistrykey deletionPolicy: WipeOut ``` +Now run the command to create this Qdrant object: + +```bash +$ kubectl apply -f https://github.com/kubedb/docs/raw/{{< param "info.version" >}}/docs/examples/qdrant/private-registry/pvt-reg-qdrant.yaml +qdrant.kubedb.com/pvt-reg-qdrant created +``` + +To check if the images pulled successfully from the registry, wait for the Qdrant to go into `Ready` state: + +```bash +$ kubectl get qdrant -n demo pvt-reg-qdrant -w +NAME VERSION STATUS AGE +pvt-reg-qdrant 1.17.0-private Provisioning 5s +pvt-reg-qdrant 1.17.0-private Ready 1m +``` + ## Cleaning up +To clean up the Kubernetes resources created by this tutorial, run: + ```bash kubectl delete qdrant -n demo pvt-reg-qdrant kubectl delete ns demo diff --git a/docs/guides/qdrant/quickstart/quickstart.md b/docs/guides/qdrant/quickstart/quickstart.md index cc52f66cc..0abfd38a4 100644 --- a/docs/guides/qdrant/quickstart/quickstart.md +++ b/docs/guides/qdrant/quickstart/quickstart.md @@ -14,35 +14,64 @@ section_menu_id: guides # Running Qdrant -This tutorial shows how to run Qdrant with KubeDB. - -> Note: YAML files used in this tutorial are stored in [docs/examples/qdrant/quickstart](https://github.com/kubedb/docs/tree/{{< param "info.version" >}}/docs/examples/qdrant/quickstart). +This tutorial will show you how to use KubeDB to run a Qdrant database. ## Before You Begin -- Prepare a Kubernetes cluster and `kubectl`. -- Install KubeDB from [/docs/setup/README.md](/docs/setup/README.md). -- This tutorial uses `docs/examples/qdrant/quickstart/distributed.yaml` as the working example manifest. -- Create namespace: +At first, you need to have a Kubernetes cluster, and the `kubectl` command-line tool must be configured to communicate with your cluster. If you do not already have a cluster, you can create one by using [kind](https://kind.sigs.k8s.io/docs/user/quick-start/). + +Now, install KubeDB cli on your workstation and KubeDB operator in your cluster following the steps [here](/docs/setup/README.md). + +To keep things isolated, this tutorial uses a separate namespace called `demo` throughout this tutorial. ```bash -kubectl create ns demo +$ kubectl create ns demo +namespace/demo created ``` -## Check Available StorageClass +> **Note:** YAML files used in this tutorial are stored in [docs/examples/qdrant/quickstart](https://github.com/kubedb/docs/tree/{{< param "info.version" >}}/docs/examples/qdrant/quickstart) folder in GitHub repository [kubedb/docs](https://github.com/kubedb/docs). + +## Find Available StorageClass + +We will need to provide `StorageClass` in the Qdrant CR specification. Check available `StorageClass` in your cluster using the following command: ```bash -kubectl get storageclass +$ kubectl get storageclass +NAME PROVISIONER RECLAIMPOLICY VOLUMEBINDINGMODE ALLOWVOLUMEEXPANSION AGE +standard (default) rancher.io/local-path Delete WaitForFirstConsumer false 10d ``` -## Check Available QdrantVersion +Here, we have `standard` StorageClass in our cluster. + +## Find Available QdrantVersion + +When you install KubeDB, it creates `QdrantVersion` CRDs for all supported Qdrant versions. Let's check available `QdrantVersion`s: ```bash -kubectl get qdrantversions +$ kubectl get qdrantversions +NAME VERSION DB_IMAGE DEPRECATED AGE +1.7.4 1.7.4 qdrant/qdrant:v1.7.4 3d +1.8.4 1.8.4 qdrant/qdrant:v1.8.4 3d +1.10.0 1.10.0 qdrant/qdrant:v1.10.0 3d +1.11.0 1.11.0 qdrant/qdrant:v1.11.0 3d +1.12.0 1.12.0 qdrant/qdrant:v1.12.0 3d +1.13.0 1.13.0 qdrant/qdrant:v1.13.0 3d +1.14.0 1.14.0 qdrant/qdrant:v1.14.0 3d +1.15.0 1.15.0 qdrant/qdrant:v1.15.0 3d +1.16.0 1.16.0 qdrant/qdrant:v1.16.0 3d +1.17.0 1.17.0 qdrant/qdrant:v1.17.0 3d ``` +Notice the `DEPRECATED` column. `true` means that QdrantVersion is deprecated for the current KubeDB version and KubeDB will not work for that version. + +In this tutorial, we will use `1.17.0` QdrantVersion CR to create a Qdrant cluster. To know more about what `QdrantVersion` CR is and why there may be variation in version names, please visit [here](/docs/guides/qdrant/concepts/catalog.md). + ## Create a Qdrant Database +KubeDB implements a `Qdrant` CRD to define the specification of a Qdrant database. + +Below is the `Qdrant` object created in this tutorial: + ```yaml apiVersion: kubedb.com/v1alpha2 kind: Qdrant @@ -50,34 +79,139 @@ metadata: name: qdrant-sample namespace: demo spec: - version: 1.17.0 - mode: Distributed + version: "1.17.0" replicas: 3 storage: + storageClassName: "standard" accessModes: - ReadWriteOnce resources: requests: - storage: 2Gi - deletionPolicy: WipeOut + storage: 1Gi + deletionPolicy: DoNotTerminate +``` + +```bash +$ kubectl create -f https://github.com/kubedb/docs/raw/{{< param "info.version" >}}/docs/examples/qdrant/quickstart/qdrant-sample.yaml +qdrant.kubedb.com/qdrant-sample created +``` + +Here, + +- `spec.version` is the name of the QdrantVersion CR where Docker images are specified. In this tutorial, a Qdrant `1.17.0` cluster is created. +- `spec.replicas` specifies the number of Qdrant nodes in the cluster. +- `spec.storage` specifies the size and StorageClass of the PVC that will be dynamically allocated to store data for each Qdrant pod. This storage spec will be passed to the StatefulSet created by KubeDB operator to run database pods. +- `spec.deletionPolicy` specifies what KubeDB should do when a user tries to delete the `Qdrant` CR. Deletion policy `DoNotTerminate` prevents deletion of this object if the admission webhook is enabled. + +> **Note:** `spec.storage` section is used to create PVC for the database pods. Specify only `requests`, not `limits` — PVC does not resize automatically. + +Now, let's watch the progress of creating the `Qdrant` cluster: + +```bash +$ kubectl get qdrant -n demo qdrant-sample -w +NAME VERSION STATUS AGE +qdrant-sample 1.17.0 Provisioning 5s +qdrant-sample 1.17.0 Provisioning 30s +qdrant-sample 1.17.0 Ready 2m ``` +## Describe Qdrant + +Let's describe the `Qdrant` object to see its current state: + +```bash +$ kubectl describe qdrant -n demo qdrant-sample +Name: qdrant-sample +Namespace: demo +Labels: +Annotations: +API Version: kubedb.com/v1alpha2 +Kind: Qdrant +Metadata: + ... +Spec: + Auth Secret: + Name: qdrant-sample-auth + Deletion Policy: DoNotTerminate + Replicas: 3 + Storage: + Access Modes: + ReadWriteOnce + Resources: + Requests: + Storage: 1Gi + Storage Class Name: standard + Storage Type: Durable + Version: 1.17.0 +Status: + Conditions: + Last Transition Time: 2024-10-01T10:00:00Z + Message: The KubeDB operator has started the provisioning of Qdrant: demo/qdrant-sample + Reason: DatabaseProvisioningStartedSuccessfully + Status: True + Type: ProvisioningStarted + Last Transition Time: 2024-10-01T10:01:30Z + Message: All desired replicas are ready. + Reason: AllReplicasReady + Status: True + Type: ReplicaReady + Last Transition Time: 2024-10-01T10:02:00Z + Message: The Qdrant: demo/qdrant-sample is accepting client requests. + Reason: DatabaseAcceptingConnectionRequest + Status: True + Type: AcceptingConnection + Last Transition Time: 2024-10-01T10:02:00Z + Message: DB is ready because of reason + Reason: ReadinessCheckSucceeded + Status: True + Type: Ready + Last Transition Time: 2024-10-01T10:02:00Z + Message: The Qdrant: demo/qdrant-sample is successfully provisioned. + Reason: DatabaseSuccessfullyProvisioned + Status: True + Type: Provisioned + Phase: Ready +``` + +## Connect to Qdrant + +KubeDB creates a Secret containing authentication credentials for the Qdrant cluster. Let's check it: + ```bash -kubectl apply -f https://github.com/kubedb/docs/raw/{{< param "info.version" >}}/docs/examples/qdrant/quickstart/distributed.yaml -kubectl get qdrant -n demo qdrant-sample -w +$ kubectl get secret -n demo qdrant-sample-auth -o yaml +apiVersion: v1 +data: + api-key: +kind: Secret +metadata: + name: qdrant-sample-auth + namespace: demo +type: Opaque ``` -## Verify Qdrant Database +Now, let's connect to the Qdrant cluster using port forwarding: ```bash -kubectl get qdrant -n demo -kubectl describe qdrant -n demo qdrant-sample +$ kubectl port-forward -n demo svc/qdrant-sample 6333:6333 & +$ export QDRANT_API_KEY=$(kubectl get secret -n demo qdrant-sample-auth -o jsonpath='{.data.api-key}' | base64 -d) + +$ curl -H "api-key: $QDRANT_API_KEY" http://localhost:6333/collections +{"result":{"collections":[]},"status":"ok","time":0.001} ``` -When `status.phase` becomes `Ready`, the Qdrant cluster is ready to accept vector search and management requests. +## Database DeletionPolicy + +This tutorial has set `deletionPolicy: DoNotTerminate`. This will prevent you from deleting the database. If you try to delete it, you will get an error. Once you are done experimenting, change the `deletionPolicy` to `WipeOut` before deleting the Qdrant CR: + +```bash +$ kubectl patch -n demo qdrant/qdrant-sample -p '{"spec":{"deletionPolicy":"WipeOut"}}' --type="merge" +qdrant.kubedb.com/qdrant-sample patched +``` ## Cleaning up +To clean up the Kubernetes resources created by this tutorial, run: + ```bash kubectl delete qdrant -n demo qdrant-sample kubectl delete ns demo diff --git a/docs/guides/qdrant/quickstart/rbac.md b/docs/guides/qdrant/quickstart/rbac.md index ab69353ce..6e24e415a 100644 --- a/docs/guides/qdrant/quickstart/rbac.md +++ b/docs/guides/qdrant/quickstart/rbac.md @@ -12,20 +12,39 @@ section_menu_id: guides > New to KubeDB? Please start [here](/docs/README.md). -# Run Qdrant with RBAC Enabled +# RBAC Permissions for Qdrant -This tutorial shows how to run Qdrant with the RBAC permissions required by KubeDB. +If RBAC is enabled in clusters, some Qdrant-specific RBAC permissions are required. These permissions are required for the KubeDB operator to manage Qdrant pods properly. + +Here is the list of additional permissions required by the StatefulSet of Qdrant: + +| Kubernetes Resource | Resource Names | Permission required | +|---------------------|-----------------|------------------------| +| statefulsets | `{qdrant-name}` | get | +| pods | | list, patch | +| pods/exec | | create | +| qdrants | | get | +| configmaps | `{qdrant-name}` | get, update, create | +| secrets | | get, list | ## Before You Begin -- Prepare a Kubernetes cluster and `kubectl`. -- Install KubeDB from [/docs/setup/README.md](/docs/setup/README.md). +At first, you need to have a Kubernetes cluster, and the `kubectl` command-line tool must be configured to communicate with your cluster. If you do not already have a cluster, you can create one by using [kind](https://kind.sigs.k8s.io/docs/user/quick-start/). + +Now, install KubeDB cli on your workstation and KubeDB operator in your cluster following the steps [here](/docs/setup/README.md). + +To keep things isolated, this tutorial uses a separate namespace called `demo` throughout this tutorial. ```bash -kubectl create ns demo +$ kubectl create ns demo +namespace/demo created ``` -## Deploy Qdrant +> **Note:** YAML files used in this tutorial are stored in [docs/examples/qdrant/quickstart](https://github.com/kubedb/docs/tree/{{< param "info.version" >}}/docs/examples/qdrant/quickstart) folder in GitHub repository [kubedb/docs](https://github.com/kubedb/docs). + +## Create a Qdrant Database + +Below is the `Qdrant` object created in this tutorial: ```yaml apiVersion: kubedb.com/v1alpha2 @@ -34,27 +53,164 @@ metadata: name: qdrant-rbac namespace: demo spec: - version: 1.17.0 - mode: Distributed + version: "1.17.0" replicas: 3 - storageType: Durable storage: + storageClassName: "standard" accessModes: - - ReadWriteOnce + - ReadWriteOnce resources: requests: - storage: 2Gi - deletionPolicy: WipeOut + storage: 1Gi + deletionPolicy: Delete ``` ```bash -kubectl apply -f https://github.com/kubedb/docs/raw/{{< param "info.version" >}}/docs/examples/qdrant/quickstart/distributed.yaml -kubectl get qdrant -n demo qdrant-rbac -w +$ kubectl create -f https://github.com/kubedb/docs/raw/{{< param "info.version" >}}/docs/examples/qdrant/quickstart/qdrant-rbac.yaml +qdrant.kubedb.com/qdrant-rbac created +``` + +When this `Qdrant` object is created, KubeDB operator creates a Role, ServiceAccount, and RoleBinding with the matching Qdrant name and uses that ServiceAccount in the corresponding StatefulSet. + +Let's see what KubeDB operator has created for additional RBAC permissions. + +### Role + +KubeDB operator creates a Role object `qdrant-rbac` in the same namespace as the Qdrant object: + +```yaml +$ kubectl get role -n demo qdrant-rbac -o yaml +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + labels: + app.kubernetes.io/component: database + app.kubernetes.io/instance: qdrant-rbac + app.kubernetes.io/managed-by: kubedb.com + app.kubernetes.io/name: qdrants.kubedb.com + name: qdrant-rbac + namespace: demo + ownerReferences: + - apiVersion: kubedb.com/v1alpha2 + blockOwnerDeletion: true + controller: true + kind: Qdrant + name: qdrant-rbac +rules: +- apiGroups: + - apps + resourceNames: + - qdrant-rbac + resources: + - statefulsets + verbs: + - get +- apiGroups: + - kubedb.com + resourceNames: + - qdrant-rbac + resources: + - qdrants + verbs: + - get +- apiGroups: + - "" + resources: + - pods + verbs: + - get + - list + - patch + - delete +- apiGroups: + - "" + resources: + - pods/exec + verbs: + - create +- apiGroups: + - "" + resources: + - secrets + verbs: + - get + - list +- apiGroups: + - "" + resources: + - configmaps + verbs: + - create + - get + - update +``` + +### ServiceAccount + +KubeDB operator creates a ServiceAccount object `qdrant-rbac` in the same namespace as the Qdrant object: + +```yaml +$ kubectl get serviceaccount -n demo qdrant-rbac -o yaml +apiVersion: v1 +kind: ServiceAccount +metadata: + labels: + app.kubernetes.io/component: database + app.kubernetes.io/instance: qdrant-rbac + app.kubernetes.io/managed-by: kubedb.com + app.kubernetes.io/name: qdrants.kubedb.com + name: qdrant-rbac + namespace: demo + ownerReferences: + - apiVersion: kubedb.com/v1alpha2 + blockOwnerDeletion: true + controller: true + kind: Qdrant + name: qdrant-rbac ``` +This ServiceAccount is used in the StatefulSet created for the Qdrant object. + +### RoleBinding + +KubeDB operator creates a RoleBinding object `qdrant-rbac` in the same namespace as the Qdrant object: + +```yaml +$ kubectl get rolebinding -n demo qdrant-rbac -o yaml +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + labels: + app.kubernetes.io/component: database + app.kubernetes.io/instance: qdrant-rbac + app.kubernetes.io/managed-by: kubedb.com + app.kubernetes.io/name: qdrants.kubedb.com + name: qdrant-rbac + namespace: demo + ownerReferences: + - apiVersion: kubedb.com/v1alpha2 + blockOwnerDeletion: true + controller: true + kind: Qdrant + name: qdrant-rbac +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: qdrant-rbac +subjects: +- kind: ServiceAccount + name: qdrant-rbac + namespace: demo +``` + +This object binds Role `qdrant-rbac` with ServiceAccount `qdrant-rbac`. + ## Cleaning up +To clean up the Kubernetes resources created by this tutorial, run: + ```bash -kubectl delete qdrant -n demo qdrant-rbac +kubectl patch -n demo qdrant/qdrant-rbac -p '{"spec":{"deletionPolicy":"WipeOut"}}' --type="merge" +kubectl delete -n demo qdrant/qdrant-rbac kubectl delete ns demo ``` \ No newline at end of file diff --git a/docs/guides/qdrant/reconfigure-tls/overview.md b/docs/guides/qdrant/reconfigure-tls/overview.md index 1d2484431..620d2fabe 100644 --- a/docs/guides/qdrant/reconfigure-tls/overview.md +++ b/docs/guides/qdrant/reconfigure-tls/overview.md @@ -12,14 +12,15 @@ section_menu_id: guides > New to KubeDB? Please start [here](/docs/README.md). -# Reconfigure TLS for Qdrant +# Reconfiguring TLS for Qdrant -This guide shows how to rotate or reconfigure TLS materials for Qdrant. +This guide will give an overview of how KubeDB Ops-manager reconfigures TLS for a `Qdrant` database. ## Before You Begin -- Install `cert-manager` in your cluster. -- Ensure KubeDB and Ops-manager are installed. +- You should be familiar with the following `KubeDB` concepts: + - [Qdrant](/docs/guides/qdrant/concepts/qdrant.md) + - [QdrantOpsRequest](/docs/guides/qdrant/concepts/opsrequest.md) - Use the example files from `docs/examples/qdrant/quickstart/distributed.yaml` and `docs/examples/qdrant/reconfigure-tls/ops-request.yaml`. ```bash @@ -33,23 +34,26 @@ kubectl apply -f https://github.com/kubedb/docs/raw/{{< param "info.version" >}} kubectl get qdrant -n demo qdrant-sample -w ``` -## Apply ReconfigureTLS OpsRequest +## How Reconfigure TLS Works -```bash -kubectl apply -f https://github.com/kubedb/docs/raw/{{< param "info.version" >}}/docs/examples/qdrant/reconfigure-tls/ops-request.yaml -kubectl get qdrantopsrequest -n demo qdrant-reconfigure-tls -``` +The Reconfigure TLS process consists of the following steps: -## Verify +1. At first, a user creates a `Qdrant` CR. -```bash -kubectl describe qdrantopsrequest -n demo qdrant-reconfigure-tls -``` +2. `KubeDB-Provisioner` operator watches the `Qdrant` CR. -## Cleaning up +3. When the operator finds a `Qdrant` CR, it creates a `StatefulSet` and related necessary stuff like secrets, services, etc. -```bash -kubectl delete qdrantopsrequest -n demo qdrant-reconfigure-tls -kubectl delete qdrant -n demo qdrant-sample -kubectl delete ns demo -``` +4. Then, in order to reconfigure TLS of the `Qdrant` database, the user creates a `QdrantOpsRequest` CR specifying the desired TLS configuration. The user can add TLS to an existing non-TLS database, rotate the existing certificates, or remove TLS entirely. + +5. `KubeDB` Ops-manager operator watches the `QdrantOpsRequest` CR. + +6. When it finds a `QdrantOpsRequest` CR, it pauses the `Qdrant` object so that the `KubeDB-Provisioner` operator doesn't perform any operations on the `Qdrant` during the TLS reconfiguration process. + +7. Then the `KubeDB` Ops-manager operator updates the TLS secrets and restarts the pods in a rolling fashion with the new TLS configuration. + +8. After the successful TLS reconfiguration, the `KubeDB` Ops-manager updates the `Qdrant` object to reflect the updated TLS state. + +9. After the successful Reconfigure TLS, the `KubeDB` Ops-manager resumes the `Qdrant` object so that the `KubeDB-Provisioner` resumes its usual operations. + +In the next doc, we are going to show a step-by-step guide on reconfiguring TLS for a Qdrant database using `QdrantOpsRequest` CRD. diff --git a/docs/guides/qdrant/reconfigure-tls/reconfigure-tls.md b/docs/guides/qdrant/reconfigure-tls/reconfigure-tls.md index 4e9b63721..8e668d4a5 100644 --- a/docs/guides/qdrant/reconfigure-tls/reconfigure-tls.md +++ b/docs/guides/qdrant/reconfigure-tls/reconfigure-tls.md @@ -12,52 +12,241 @@ section_menu_id: guides > New to KubeDB? Please start [here](/docs/README.md). -# Reconfigure TLS in Qdrant +# Reconfigure Qdrant TLS/SSL (Transport Encryption) -This guide shows how to reconfigure or rotate TLS certificates of a Qdrant database using `QdrantOpsRequest`. +KubeDB supports reconfiguring TLS/SSL certificates for Qdrant — adding, removing, updating, and rotating certificates via a `QdrantOpsRequest`. This tutorial will show you how to use KubeDB to reconfigure TLS/SSL encryption. ## Before You Begin -- Install KubeDB Community and Enterprise operators. -- Ensure your database is already running with TLS enabled. +- At first, you need to have a Kubernetes cluster, and the `kubectl` command-line tool must be configured to communicate with your cluster. If you do not already have a cluster, you can create one by using [kind](https://kind.sigs.k8s.io/docs/user/quick-start/). + +- Install `cert-manager` v1.0.0 or later to your cluster to manage your SSL/TLS certificates from [here](https://cert-manager.io/docs/installation/). + +- Now, install `KubeDB` in your cluster following the steps [here](/docs/setup/README.md). + +- You should be familiar with the following `KubeDB` concepts: + - [Qdrant](/docs/guides/qdrant/concepts/qdrant.md) + - [QdrantOpsRequest](/docs/guides/qdrant/concepts/opsrequest.md) + - [TLS/SSL Overview](/docs/guides/qdrant/tls/overview.md) + +To keep things isolated, this tutorial uses a separate namespace called `demo` throughout this tutorial. ```bash $ kubectl create ns demo +namespace/demo created ``` -## Apply ReconfigureTLS OpsRequest +> **Note:** YAML files used in this tutorial are stored in [docs/guides/qdrant/reconfigure-tls/yamls](/docs/guides/qdrant/reconfigure-tls/yamls) directory of [kubedb/docs](https://github.com/kubedb/docs) repository. + +## Add TLS to a Qdrant database + +Here, we are going to create a Qdrant database without TLS and then reconfigure the database to use TLS. + +### Deploy Qdrant without TLS + +In this section, we are going to deploy a Qdrant cluster without TLS. Below is the YAML of the `Qdrant` CR that we are going to create: + +```yaml +apiVersion: kubedb.com/v1alpha2 +kind: Qdrant +metadata: + name: qdrant-sample + namespace: demo +spec: + version: "1.17.0" + replicas: 3 + storage: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 1Gi + deletionPolicy: WipeOut +``` + +Let's create the `Qdrant` CR we have shown above: + +```bash +$ kubectl create -f https://github.com/kubedb/docs/raw/{{< param "info.version" >}}/docs/guides/qdrant/reconfigure-tls/yamls/qdrant.yaml +qdrant.kubedb.com/qdrant-sample created +``` + +Now, wait until `qdrant-sample` has status `Ready`: + +```bash +$ kubectl get qdrant -n demo +NAME VERSION STATUS AGE +qdrant-sample 1.17.0 Ready 3m22s +``` + +### Create Issuer + +Now, we are going to create an example `Issuer` that will be used to enable SSL/TLS in Qdrant. Alternatively you can follow this [cert-manager tutorial](https://cert-manager.io/docs/configuration/ca/) to create your own `Issuer`. By following the below steps, we are going to create our desired issuer, + +1. Start off by generating our ca-certificates using openssl, + +```bash +$ openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout ./ca.key -out ./ca.crt -subj "/CN=qdrant/O=kubedb" +Generating a RSA private key +................+++++ +........................+++++ +writing new private key to './ca.key' +``` + +2. Create a secret using the certificate files we have just generated, + +```bash +$ kubectl create secret tls qdrant-ca --cert=ca.crt --key=ca.key --namespace=demo +secret/qdrant-ca created +``` + +3. Now we are going to create an `Issuer` using the `qdrant-ca` secret that contains the CA certificate we have just created: + +```yaml +apiVersion: cert-manager.io/v1 +kind: Issuer +metadata: + name: qdrant-issuer + namespace: demo +spec: + ca: + secretName: qdrant-ca +``` + +```bash +$ kubectl apply -f https://github.com/kubedb/docs/raw/{{< param "info.version" >}}/docs/guides/qdrant/reconfigure-tls/yamls/issuer.yaml +issuer.cert-manager.io/qdrant-issuer created +``` + +### Add TLS + +Now, we are going to create a `QdrantOpsRequest` to add TLS to the running database. ```yaml apiVersion: ops.kubedb.com/v1alpha1 kind: QdrantOpsRequest metadata: - name: qdrant-reconfigure-tls + name: qdops-add-tls namespace: demo spec: type: ReconfigureTLS databaseRef: - name: tls-qdrant + name: qdrant-sample + tls: + issuerRef: + name: qdrant-issuer + kind: Issuer + apiGroup: "cert-manager.io" + certificates: + - alias: server + subject: + organizations: + - kubedb:server + dnsNames: + - localhost + ipAddresses: + - "127.0.0.1" +``` + +Here, + +- `spec.databaseRef.name` specifies that we are performing reconfigure TLS operation on `qdrant-sample` database. +- `spec.type` specifies that we are performing `ReconfigureTLS` on our database. +- `spec.tls.issuerRef` specifies the issuer to use for signing certificates. +- `spec.tls.certificates` specifies the certificate configuration. + +```bash +$ kubectl apply -f https://github.com/kubedb/docs/raw/{{< param "info.version" >}}/docs/guides/qdrant/reconfigure-tls/yamls/add-tls.yaml +qdrantopsrequest.ops.kubedb.com/qdops-add-tls created +``` + +Let's wait for `QdrantOpsRequest` to be `Successful`: + +```bash +$ watch -n 3 kubectl get QdrantOpsRequest -n demo qdops-add-tls +Every 3.0s: kubectl get QdrantOpsRequest -n demo qdops-add-tls + +NAME TYPE STATUS AGE +qdops-add-tls ReconfigureTLS Successful 6m30s +``` + +## Rotate Certificates + +Now we are going to rotate the certificates of the database. + +```yaml +apiVersion: ops.kubedb.com/v1alpha1 +kind: QdrantOpsRequest +metadata: + name: qdops-rotate-tls + namespace: demo +spec: + type: ReconfigureTLS + databaseRef: + name: qdrant-sample tls: rotateCertificates: true - client: true - p2p: true ``` - ```bash - $ kubectl apply -f qdrant-reconfigure-tls.yaml - qdrantopsrequest.ops.kubedb.com/qdrant-reconfigure-tls created - ``` +Here, + +- `spec.tls.rotateCertificates` specifies that we are requesting to rotate the certificates of the `qdrant-sample` database. + +```bash +$ kubectl apply -f https://github.com/kubedb/docs/raw/{{< param "info.version" >}}/docs/guides/qdrant/reconfigure-tls/yamls/rotate-tls.yaml +qdrantopsrequest.ops.kubedb.com/qdops-rotate-tls created +``` + +Let's wait for `QdrantOpsRequest` to be `Successful`: + +```bash +$ kubectl get qdrantopsrequest -n demo qdops-rotate-tls +NAME TYPE STATUS AGE +qdops-rotate-tls ReconfigureTLS Successful 3m8s +``` + +## Remove TLS from the Database + +In this section, we are going to reconfigure TLS setting of the database by removing the TLS configuration. + +```yaml +apiVersion: ops.kubedb.com/v1alpha1 +kind: QdrantOpsRequest +metadata: + name: qdops-remove-tls + namespace: demo +spec: + type: ReconfigureTLS + databaseRef: + name: qdrant-sample + tls: + remove: true +``` + +Here, + +- `spec.tls.remove` specifies that we are removing the TLS configuration from `qdrant-sample` database. + +```bash +$ kubectl apply -f https://github.com/kubedb/docs/raw/{{< param "info.version" >}}/docs/guides/qdrant/reconfigure-tls/yamls/remove-tls.yaml +qdrantopsrequest.ops.kubedb.com/qdops-remove-tls created +``` + +Let's wait for `QdrantOpsRequest` to be `Successful`: - ## Verify +```bash +$ kubectl get qdrantopsrequest -n demo qdops-remove-tls +NAME TYPE STATUS AGE +qdops-remove-tls ReconfigureTLS Successful 4m20s +``` - ```bash - $ kubectl get qdrantopsrequest -n demo qdrant-reconfigure-tls - $ kubectl describe qdrantopsrequest -n demo qdrant-reconfigure-tls - ``` +## Cleaning up - ## Cleaning up +To clean up the Kubernetes resources created by this tutorial, run: - ```bash - kubectl delete qdrantopsrequest -n demo qdrant-reconfigure-tls - kubectl delete ns demo - ``` \ No newline at end of file +```bash +kubectl delete qdrantopsrequest -n demo qdops-add-tls qdops-rotate-tls qdops-remove-tls +kubectl delete qdrant -n demo qdrant-sample +kubectl delete issuer -n demo qdrant-issuer +kubectl delete ns demo +``` \ No newline at end of file diff --git a/docs/guides/qdrant/reconfigure/overview.md b/docs/guides/qdrant/reconfigure/overview.md index d35656deb..5b0261ad8 100644 --- a/docs/guides/qdrant/reconfigure/overview.md +++ b/docs/guides/qdrant/reconfigure/overview.md @@ -14,41 +14,34 @@ section_menu_id: guides # Reconfiguring Qdrant -This guide shows how to update runtime configuration of Qdrant using `QdrantOpsRequest`. +This guide will give an overview of how KubeDB Ops-manager reconfigures a `Qdrant` database. ## Before You Begin -- Ensure KubeDB and Ops-manager are installed. -- Use the example files from `docs/examples/qdrant/quickstart/distributed.yaml` and `docs/examples/qdrant/reconfigure/ops-request.yaml`. +- You should be familiar with the following `KubeDB` concepts: + - [Qdrant](/docs/guides/qdrant/concepts/qdrant.md) + - [QdrantOpsRequest](/docs/guides/qdrant/concepts/opsrequest.md) -```bash -kubectl create ns demo -``` +## How Reconfiguration Works -## Deploy Qdrant +The Reconfiguration process consists of the following steps: -```bash -kubectl apply -f https://github.com/kubedb/docs/raw/{{< param "info.version" >}}/docs/examples/qdrant/quickstart/distributed.yaml -kubectl get qdrant -n demo qdrant-sample -w -``` +1. At first, a user creates a `Qdrant` CR. -## Apply Reconfigure OpsRequest +2. `KubeDB-Provisioner` operator watches the `Qdrant` CR. -```bash -kubectl apply -f https://github.com/kubedb/docs/raw/{{< param "info.version" >}}/docs/examples/qdrant/reconfigure/ops-request.yaml -kubectl get qdrantopsrequest -n demo qdrant-reconfigure -``` +3. When the operator finds a `Qdrant` CR, it creates a `StatefulSet` and related necessary stuff like secrets, services, etc. -## Verify +4. Then, in order to reconfigure the `Qdrant` database, the user creates a `QdrantOpsRequest` CR with the new configuration. The user can provide the new configuration either via a new config secret, via `applyConfig`, or by removing the custom configuration (reverting to defaults). -```bash -kubectl describe qdrantopsrequest -n demo qdrant-reconfigure -``` +5. `KubeDB` Ops-manager operator watches the `QdrantOpsRequest` CR. -## Cleaning up +6. When it finds a `QdrantOpsRequest` CR, it pauses the `Qdrant` object so that the `KubeDB-Provisioner` operator doesn't perform any operations on the `Qdrant` during the reconfiguration process. -```bash -kubectl delete qdrantopsrequest -n demo qdrant-reconfigure -kubectl delete qdrant -n demo qdrant-sample -kubectl delete ns demo -``` +7. Then the `KubeDB` Ops-manager operator updates the configuration secret and restarts the pods in a rolling fashion to apply the new configuration. + +8. After the successful configuration update, the `KubeDB` Ops-manager updates the `Qdrant` object to reflect the updated configuration state. + +9. After the successful reconfiguration, the `KubeDB` Ops-manager resumes the `Qdrant` object so that the `KubeDB-Provisioner` resumes its usual operations. + +In the next doc, we are going to show a step-by-step guide on reconfiguring a Qdrant database using `QdrantOpsRequest` CRD. diff --git a/docs/guides/qdrant/reconfigure/reconfigure.md b/docs/guides/qdrant/reconfigure/reconfigure.md index fa1df1d3c..85fe6db61 100644 --- a/docs/guides/qdrant/reconfigure/reconfigure.md +++ b/docs/guides/qdrant/reconfigure/reconfigure.md @@ -14,24 +14,116 @@ section_menu_id: guides # Reconfigure Qdrant -This guide shows how to apply configuration changes to a running Qdrant database using `QdrantOpsRequest`. +This guide will show you how to use `KubeDB` Enterprise operator to reconfigure a Qdrant cluster. ## Before You Begin -- Install KubeDB Community and Enterprise operators from [setup guide](/docs/setup/README.md). -- Deploy a Qdrant database and ensure it is in `Ready` state. +- At first, you need to have a Kubernetes cluster, and the `kubectl` command-line tool must be configured to communicate with your cluster. + +- Install `KubeDB` Community and Enterprise operator in your cluster following the steps [here](/docs/setup/README.md). + +- You should be familiar with the following `KubeDB` concepts: + - [Qdrant](/docs/guides/qdrant/concepts/qdrant.md) + - [QdrantOpsRequest](/docs/guides/qdrant/concepts/opsrequest.md) + - [Reconfigure Overview](/docs/guides/qdrant/reconfigure/overview.md) + +To keep everything isolated, we are going to use a separate namespace called `demo` throughout this tutorial. ```bash $ kubectl create ns demo +namespace/demo created +``` + +> **Note:** YAML files used in this tutorial are stored in [docs/guides/qdrant/reconfigure/yamls](/docs/guides/qdrant/reconfigure/yamls) directory of [kubedb/docs](https://github.com/kubedb/docs) repository. + +### Prepare Qdrant + +Now, we are going to deploy a `Qdrant` cluster with an initial configuration. + +#### Deploy Qdrant with custom config + +First, we will create a `qdrant.yaml` config file containing our initial configuration settings. + +```yaml +# qdrant.yaml +storage: + performance: + max_search_threads: 4 +``` + +Now, we will create a secret with this configuration file. + +```bash +$ kubectl create secret generic -n demo qdrant-configuration --from-file=./qdrant.yaml +secret/qdrant-configuration created ``` -## Apply Reconfigure OpsRequest +Below is the YAML of the `Qdrant` CR that we are going to create: + +```yaml +apiVersion: kubedb.com/v1alpha2 +kind: Qdrant +metadata: + name: qdrant-sample + namespace: demo +spec: + version: "1.17.0" + replicas: 3 + configSecret: + name: qdrant-configuration + storage: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 1Gi + deletionPolicy: WipeOut +``` + +Let's create the `Qdrant` CR we have shown above: + +```bash +$ kubectl create -f https://github.com/kubedb/docs/raw/{{< param "info.version" >}}/docs/guides/qdrant/reconfigure/yamls/qdrant.yaml +qdrant.kubedb.com/qdrant-sample created +``` + +Now, wait until `qdrant-sample` has status `Ready`: + +```bash +$ kubectl get qdrant -n demo +NAME VERSION STATUS AGE +qdrant-sample 1.17.0 Ready 3m42s +``` + +### Reconfigure using new config secret + +Now we will reconfigure this database to change `max_search_threads` to `8`. + +First, we will create a new `qdrant.yaml` file containing the updated configuration: + +```yaml +# qdrant.yaml +storage: + performance: + max_search_threads: 8 +``` + +Then, we will create a new secret with this configuration file: + +```bash +$ kubectl create secret generic -n demo new-qdrant-configuration --from-file=./qdrant.yaml +secret/new-qdrant-configuration created +``` + +#### Create QdrantOpsRequest + +Now, we will use this secret to replace the previous secret using a `QdrantOpsRequest` CR. Below is the YAML of the `QdrantOpsRequest` that we are going to create: ```yaml apiVersion: ops.kubedb.com/v1alpha1 kind: QdrantOpsRequest metadata: - name: qdrant-reconfigure + name: qdops-reconfigure-config namespace: demo spec: type: Reconfigure @@ -39,24 +131,118 @@ spec: name: qdrant-sample configuration: configSecret: - name: qdrant-config-updated + name: new-qdrant-configuration +``` + +Here, + +- `spec.databaseRef.name` specifies that we are reconfiguring `qdrant-sample` database. +- `spec.type` specifies that we are performing `Reconfigure` on our database. +- `spec.configuration.configSecret.name` specifies the name of the new configuration secret. + +Let's create the `QdrantOpsRequest` CR we have shown above: + +```bash +$ kubectl apply -f https://github.com/kubedb/docs/raw/{{< param "info.version" >}}/docs/guides/qdrant/reconfigure/yamls/reconfigure-using-secret.yaml +qdrantopsrequest.ops.kubedb.com/qdops-reconfigure-config created ``` +#### Verify the new configuration is working + +If everything goes well, `KubeDB` Enterprise operator will update the `configSecret` of the `Qdrant` object. + +Let's wait for `QdrantOpsRequest` to be `Successful`: + +```bash +$ kubectl get qdops -n demo +NAME TYPE STATUS AGE +qdops-reconfigure-config Reconfigure Successful 3m21s +``` + +### Reconfigure using applyConfig + +We can also reconfigure our existing secret by modifying configuration inline using `applyConfig`. Below is the YAML of the `QdrantOpsRequest`: + +```yaml +apiVersion: ops.kubedb.com/v1alpha1 +kind: QdrantOpsRequest +metadata: + name: qdops-reconfigure-apply-config + namespace: demo +spec: + type: Reconfigure + databaseRef: + name: qdrant-sample + configuration: + applyConfig: + qdrant.yaml: | + storage: + performance: + max_search_threads: 6 +``` + +> **Note:** You can modify multiple fields of your current configuration using `applyConfig`. If you don't have any existing config secret, `applyConfig` will create a new secret for you. + +Here, + +- `spec.databaseRef.name` specifies that we are reconfiguring `qdrant-sample` database. +- `spec.type` specifies that we are performing `Reconfigure` on our database. +- `spec.configuration.applyConfig` contains the inline configuration to apply. + ```bash -$ kubectl apply -f qdrant-reconfigure.yaml -qdrantopsrequest.ops.kubedb.com/qdrant-reconfigure created +$ kubectl apply -f https://github.com/kubedb/docs/raw/{{< param "info.version" >}}/docs/guides/qdrant/reconfigure/yamls/apply-config.yaml +qdrantopsrequest.ops.kubedb.com/qdops-reconfigure-apply-config created ``` -## Verify +#### Verify the new configuration is working + +Let's wait for `QdrantOpsRequest` to be `Successful`: ```bash -$ kubectl get qdrantopsrequest -n demo qdrant-reconfigure -$ kubectl describe qdrantopsrequest -n demo qdrant-reconfigure +$ kubectl get qdops qdops-reconfigure-apply-config -n demo +NAME TYPE STATUS AGE +qdops-reconfigure-apply-config Reconfigure Successful 4m59s ``` +### Remove Custom Configuration + +We can also remove existing custom config using `QdrantOpsRequest`. Set `spec.configuration.removeCustomConfig: true` to remove the existing custom configuration. + +```yaml +apiVersion: ops.kubedb.com/v1alpha1 +kind: QdrantOpsRequest +metadata: + name: qdops-reconfigure-remove + namespace: demo +spec: + type: Reconfigure + databaseRef: + name: qdrant-sample + configuration: + removeCustomConfig: true +``` + +```bash +$ kubectl apply -f https://github.com/kubedb/docs/raw/{{< param "info.version" >}}/docs/guides/qdrant/reconfigure/yamls/remove-config.yaml +qdrantopsrequest.ops.kubedb.com/qdops-reconfigure-remove created +``` + +Let's wait for `QdrantOpsRequest` to be `Successful`: + +```bash +$ kubectl get qdops qdops-reconfigure-remove -n demo +NAME TYPE STATUS AGE +qdops-reconfigure-remove Reconfigure Successful 2m10s +``` + +After this, the `Qdrant` CR will no longer reference a `configSecret` and the database will use its default configuration. + ## Cleaning up +To clean up the Kubernetes resources created by this tutorial, run: + ```bash -kubectl delete qdrantopsrequest -n demo qdrant-reconfigure +kubectl delete qdrantopsrequest -n demo qdops-reconfigure-config qdops-reconfigure-apply-config qdops-reconfigure-remove +kubectl delete qdrant -n demo qdrant-sample kubectl delete ns demo ``` \ No newline at end of file diff --git a/docs/guides/qdrant/restart/restart.md b/docs/guides/qdrant/restart/restart.md index d04dccfde..a7e23d106 100644 --- a/docs/guides/qdrant/restart/restart.md +++ b/docs/guides/qdrant/restart/restart.md @@ -14,42 +14,152 @@ section_menu_id: guides # Restart Qdrant -This guide shows how to restart Qdrant pods using `QdrantOpsRequest`. +KubeDB supports restarting the Qdrant database via a `QdrantOpsRequest`. Restarting is useful if some pods are stuck in a non-running phase or are not working correctly. This tutorial will show you how to use that. ## Before You Begin -- Ensure KubeDB and Ops-manager are installed. -- Use the example files from `docs/examples/qdrant/quickstart/distributed.yaml` and `docs/examples/qdrant/restart/ops-request.yaml`. -- Use a separate namespace: +- At first, you need to have a Kubernetes cluster, and the `kubectl` command-line tool must be configured to communicate with your cluster. If you do not already have a cluster, you can create one by using [kind](https://kind.sigs.k8s.io/docs/user/quick-start/). + +- Now, install `KubeDB` in your cluster following the steps [here](/docs/setup/README.md). + +- You should be familiar with the following `KubeDB` concepts: + - [Qdrant](/docs/guides/qdrant/concepts/qdrant.md) + - [QdrantOpsRequest](/docs/guides/qdrant/concepts/opsrequest.md) + +To keep things isolated, this tutorial uses a separate namespace called `demo` throughout this tutorial. ```bash -kubectl create ns demo +$ kubectl create ns demo +namespace/demo created ``` +> **Note:** YAML files used in this tutorial are stored in [docs/examples/qdrant/restart](https://github.com/kubedb/docs/tree/{{< param "info.version" >}}/docs/examples/qdrant/restart) folder in GitHub repository [kubedb/docs](https://github.com/kubedb/docs). + ## Deploy Qdrant +In this section, we are going to deploy a Qdrant database using KubeDB. + +```yaml +apiVersion: kubedb.com/v1alpha2 +kind: Qdrant +metadata: + name: qdrant-sample + namespace: demo +spec: + version: "1.17.0" + replicas: 3 + storage: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 1Gi + deletionPolicy: WipeOut +``` + +Let's create the `Qdrant` CR we have shown above, + +```bash +$ kubectl create -f https://github.com/kubedb/docs/raw/{{< param "info.version" >}}/docs/examples/qdrant/restart/qdrant.yaml +qdrant.kubedb.com/qdrant-sample created +``` + +Now, wait until `qdrant-sample` has status `Ready`: + ```bash -kubectl apply -f https://github.com/kubedb/docs/raw/{{< param "info.version" >}}/docs/examples/qdrant/quickstart/distributed.yaml -kubectl get qdrant -n demo qdrant-sample -w +$ kubectl get qdrant -n demo +NAME VERSION STATUS AGE +qdrant-sample 1.17.0 Ready 3m47s ``` ## Apply Restart OpsRequest +```yaml +apiVersion: ops.kubedb.com/v1alpha1 +kind: QdrantOpsRequest +metadata: + name: qdops-restart + namespace: demo +spec: + type: Restart + databaseRef: + name: qdrant-sample + timeout: 3m + apply: Always +``` + +- `spec.type` specifies the type of the OpsRequest. +- `spec.databaseRef` holds the name of the Qdrant database. The db should be available in the same namespace as the OpsRequest. +- The meaning of `spec.timeout` & `spec.apply` fields can be found [here](/docs/guides/qdrant/concepts/opsrequest.md). + +Let's create the `QdrantOpsRequest` CR we have shown above, + ```bash -kubectl apply -f https://github.com/kubedb/docs/raw/{{< param "info.version" >}}/docs/examples/qdrant/restart/ops-request.yaml -kubectl get qdrantopsrequest -n demo qdrant-restart +$ kubectl create -f https://github.com/kubedb/docs/raw/{{< param "info.version" >}}/docs/examples/qdrant/restart/ops.yaml +qdrantopsrequest.ops.kubedb.com/qdops-restart created ``` -## Verify +Now the Ops-manager operator will restart the Qdrant pods one by one, waiting for each pod to come back to `Running` state before proceeding to the next. ```bash -kubectl describe qdrantopsrequest -n demo qdrant-restart +$ kubectl get qdops -n demo qdops-restart +NAME TYPE STATUS AGE +qdops-restart Restart Successful 3m25s +``` + +```bash +$ kubectl get qdops -n demo qdops-restart -o yaml +apiVersion: ops.kubedb.com/v1alpha1 +kind: QdrantOpsRequest +metadata: + name: qdops-restart + namespace: demo +spec: + apply: Always + databaseRef: + name: qdrant-sample + timeout: 3m + type: Restart +status: + conditions: + - lastTransitionTime: "2026-05-01T10:00:00Z" + message: Qdrant ops request is restarting nodes + observedGeneration: 1 + reason: Restart + status: "True" + type: Restart + - lastTransitionTime: "2026-05-01T10:02:30Z" + message: Successfully restarted all nodes + observedGeneration: 1 + reason: RestartNodes + status: "True" + type: RestartNodes + - lastTransitionTime: "2026-05-01T10:00:10Z" + message: evict pod; ConditionStatus:True + observedGeneration: 1 + status: "True" + type: EvictPod + - lastTransitionTime: "2026-05-01T10:01:05Z" + message: check pod ready; ConditionStatus:True + observedGeneration: 1 + status: "True" + type: CheckPodReady + - lastTransitionTime: "2026-05-01T10:02:30Z" + message: Successfully completed the modification process. + observedGeneration: 1 + reason: Successful + status: "True" + type: Successful + observedGeneration: 1 + phase: Successful ``` ## Cleaning up +To cleanup the Kubernetes resources created by this tutorial, run: + ```bash -kubectl delete qdrantopsrequest -n demo qdrant-restart +kubectl delete qdrantopsrequest -n demo qdops-restart kubectl delete qdrant -n demo qdrant-sample kubectl delete ns demo ``` diff --git a/docs/guides/qdrant/rotate-auth/overview.md b/docs/guides/qdrant/rotate-auth/overview.md index 4b483a982..ec0c467ba 100644 --- a/docs/guides/qdrant/rotate-auth/overview.md +++ b/docs/guides/qdrant/rotate-auth/overview.md @@ -12,43 +12,36 @@ section_menu_id: guides > New to KubeDB? Please start [here](/docs/README.md). -# Rotate Auth for Qdrant +# Rotating Qdrant Authentication Credentials -This guide shows how to rotate Qdrant authentication credentials. +This guide will give an overview of how KubeDB Ops-manager rotates the authentication credentials of a `Qdrant` database. ## Before You Begin -- Install KubeDB and Ops-manager from [here](/docs/setup/README.md). -- Use the example files from `docs/examples/qdrant/quickstart/distributed.yaml` and `docs/examples/qdrant/rotate-auth/ops-request.yaml`. +- You should be familiar with the following `KubeDB` concepts: + - [Qdrant](/docs/guides/qdrant/concepts/qdrant.md) + - [QdrantOpsRequest](/docs/guides/qdrant/concepts/opsrequest.md) -```bash -kubectl create ns demo -``` +## How Rotate Auth Works -## Deploy Qdrant +The Rotate Auth process consists of the following steps: -```bash -kubectl apply -f https://github.com/kubedb/docs/raw/{{< param "info.version" >}}/docs/examples/qdrant/quickstart/distributed.yaml -kubectl get qdrant -n demo qdrant-sample -w -``` +1. At first, a user creates a `Qdrant` CR. -## Apply RotateAuth OpsRequest +2. `KubeDB-Provisioner` operator watches the `Qdrant` CR. -```bash -kubectl apply -f https://github.com/kubedb/docs/raw/{{< param "info.version" >}}/docs/examples/qdrant/rotate-auth/ops-request.yaml -kubectl get qdrantopsrequest -n demo qdrant-rotate-auth -``` +3. When the operator finds a `Qdrant` CR, it creates a `StatefulSet` and generates an `authSecret` containing the initial API key for the Qdrant database. -## Verify +4. Then, in order to rotate the authentication credentials, the user creates a `QdrantOpsRequest` CR with `type: RotateAuth`. The user can optionally provide a new custom secret, or let KubeDB auto-generate a new API key. -```bash -kubectl describe qdrantopsrequest -n demo qdrant-rotate-auth -``` +5. `KubeDB` Ops-manager operator watches the `QdrantOpsRequest` CR. -## Cleaning up +6. When it finds a `QdrantOpsRequest` CR, it pauses the `Qdrant` object so that the `KubeDB-Provisioner` operator doesn't perform any operations on the `Qdrant` during the credential rotation process. -```bash -kubectl delete qdrantopsrequest -n demo qdrant-rotate-auth -kubectl delete qdrant -n demo qdrant-sample -kubectl delete ns demo -``` +7. Then the `KubeDB` Ops-manager operator generates a new API key (or uses the provided secret), updates the `authSecret`, and restarts the pods in a rolling fashion to apply the new credentials. + +8. After the successful credential rotation, the `KubeDB` Ops-manager updates the `Qdrant` object to reflect the updated auth state. + +9. After the successful Rotate Auth, the `KubeDB` Ops-manager resumes the `Qdrant` object so that the `KubeDB-Provisioner` resumes its usual operations. + +In the next doc, we are going to show a step-by-step guide on rotating authentication credentials of a Qdrant database using `QdrantOpsRequest` CRD. diff --git a/docs/guides/qdrant/rotate-auth/rotateauth.md b/docs/guides/qdrant/rotate-auth/rotateauth.md index c2658654c..b2f44ee26 100644 --- a/docs/guides/qdrant/rotate-auth/rotateauth.md +++ b/docs/guides/qdrant/rotate-auth/rotateauth.md @@ -12,26 +12,82 @@ section_menu_id: guides > New to KubeDB? Please start [here](/docs/README.md). -# Rotate Auth for Qdrant +# Rotate Authentication for Qdrant -This guide shows how to rotate authentication credentials for Qdrant using `QdrantOpsRequest`. +KubeDB supports rotating authentication credentials (API key) for Qdrant via a `QdrantOpsRequest`. This tutorial will show you how to use KubeDB to rotate authentication credentials. ## Before You Begin -- Install KubeDB Community and Enterprise operators. -- Ensure your target `Qdrant` instance is `Ready`. +- At first, you need to have a Kubernetes cluster, and the `kubectl` command-line tool must be configured to communicate with your cluster. If you do not already have a cluster, you can create one by using [kind](https://kind.sigs.k8s.io/docs/user/quick-start/). + +- Now, install `KubeDB` in your cluster following the steps [here](/docs/setup/README.md). + +- You should be familiar with the following `KubeDB` concepts: + - [Qdrant](/docs/guides/qdrant/concepts/qdrant.md) + - [QdrantOpsRequest](/docs/guides/qdrant/concepts/opsrequest.md) + +To keep things isolated, this tutorial uses a separate namespace called `demo` throughout this tutorial. ```bash $ kubectl create ns demo +namespace/demo created +``` + +> **Note:** YAML files used in this tutorial are stored in [docs/examples/qdrant/rotate-auth](https://github.com/kubedb/docs/tree/{{< param "info.version" >}}/docs/examples/qdrant/rotate-auth) folder in GitHub repository [kubedb/docs](https://github.com/kubedb/docs). + +## Deploy Qdrant + +In this section, we are going to deploy a Qdrant database using KubeDB. + +```yaml +apiVersion: kubedb.com/v1alpha2 +kind: Qdrant +metadata: + name: qdrant-sample + namespace: demo +spec: + version: "1.17.0" + replicas: 3 + storage: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 1Gi + deletionPolicy: WipeOut +``` + +Let's create the `Qdrant` CR we have shown above: + +```bash +$ kubectl create -f https://github.com/kubedb/docs/raw/{{< param "info.version" >}}/docs/examples/qdrant/rotate-auth/qdrant.yaml +qdrant.kubedb.com/qdrant-sample created +``` + +Now, wait until `qdrant-sample` has status `Ready`: + +```bash +$ kubectl get qdrant -n demo +NAME VERSION STATUS AGE +qdrant-sample 1.17.0 Ready 3m22s +``` + +When Qdrant is deployed, KubeDB creates a secret called `qdrant-sample-auth` (format: `{db-name}-auth`) that stores the API key used for authentication. + +```bash +$ kubectl get secret -n demo qdrant-sample-auth -o jsonpath='{.data.api-key}' | base64 --decode + ``` ## Apply RotateAuth OpsRequest +Now, we are going to create a `QdrantOpsRequest` to rotate the authentication credentials. + ```yaml apiVersion: ops.kubedb.com/v1alpha1 kind: QdrantOpsRequest metadata: - name: qdrant-rotate-auth + name: qdops-rotate-auth namespace: demo spec: type: RotateAuth @@ -39,21 +95,71 @@ spec: name: qdrant-sample ``` +Here, + +- `spec.databaseRef.name` specifies that we are rotating auth credentials for `qdrant-sample` Qdrant database. +- `spec.type` specifies that we are performing `RotateAuth` on our database. + +Let's create the `QdrantOpsRequest` CR we have shown above: + ```bash -$ kubectl apply -f qdrant-rotate-auth.yaml -qdrantopsrequest.ops.kubedb.com/qdrant-rotate-auth created +$ kubectl apply -f https://github.com/kubedb/docs/raw/{{< param "info.version" >}}/docs/examples/qdrant/rotate-auth/ops.yaml +qdrantopsrequest.ops.kubedb.com/qdops-rotate-auth created ``` -## Verify +## Verify Authentication Rotated + +If everything goes well, `KubeDB` ops-manager operator will rotate the authentication credentials of the `Qdrant` database. + +Let's wait for `QdrantOpsRequest` to be `Successful`: + +```bash +$ watch -n 3 kubectl get QdrantOpsRequest -n demo qdops-rotate-auth +Every 3.0s: kubectl get QdrantOpsRequest -n demo qdops-rotate-auth + +NAME TYPE STATUS AGE +qdops-rotate-auth RotateAuth Successful 2m15s +``` + +We can see from the above output that the `QdrantOpsRequest` has succeeded. Now let's check if the authentication secret has been updated: + +```bash +$ kubectl get secret -n demo qdrant-sample-auth -o jsonpath='{.data.api-key}' | base64 --decode + +``` + +You can see that the API key has been rotated. The new key is different from the initial key. KubeDB has automatically updated the Qdrant instances to use the new credentials. + +## Rotate Auth with a Custom Secret + +You can also rotate the authentication credentials using a custom secret that you provide: + +```yaml +apiVersion: ops.kubedb.com/v1alpha1 +kind: QdrantOpsRequest +metadata: + name: qdops-rotate-auth-custom + namespace: demo +spec: + type: RotateAuth + databaseRef: + name: qdrant-sample + authentication: + secretRef: + name: my-custom-auth-secret +``` ```bash -$ kubectl get qdrantopsrequest -n demo qdrant-rotate-auth -$ kubectl describe qdrantopsrequest -n demo qdrant-rotate-auth +$ kubectl apply -f https://github.com/kubedb/docs/raw/{{< param "info.version" >}}/docs/examples/qdrant/rotate-auth/ops-custom.yaml +qdrantopsrequest.ops.kubedb.com/qdops-rotate-auth-custom created ``` ## Cleaning up +To clean up the Kubernetes resources created by this tutorial, run: + ```bash -kubectl delete qdrantopsrequest -n demo qdrant-rotate-auth +kubectl delete qdrantopsrequest -n demo qdops-rotate-auth qdops-rotate-auth-custom +kubectl delete qdrant -n demo qdrant-sample kubectl delete ns demo ``` \ No newline at end of file diff --git a/docs/guides/qdrant/scaling/horizontal-scaling/overview.md b/docs/guides/qdrant/scaling/horizontal-scaling/overview.md index a4f156cb7..1429b7f32 100644 --- a/docs/guides/qdrant/scaling/horizontal-scaling/overview.md +++ b/docs/guides/qdrant/scaling/horizontal-scaling/overview.md @@ -14,41 +14,34 @@ section_menu_id: guides # Qdrant Horizontal Scaling -This guide shows how to scale Qdrant nodes horizontally. +This guide will give an overview of how KubeDB Ops-manager scales the number of nodes in a `Qdrant` database cluster. ## Before You Begin -- Ensure database is healthy (`status.phase=Ready`). -- Use the example files from `docs/examples/qdrant/quickstart/distributed.yaml` and `docs/examples/qdrant/scaling/horizontal-scaling/ops-request.yaml`. +- You should be familiar with the following `KubeDB` concepts: + - [Qdrant](/docs/guides/qdrant/concepts/qdrant.md) + - [QdrantOpsRequest](/docs/guides/qdrant/concepts/opsrequest.md) -```bash -kubectl create ns demo -``` +## How Horizontal Scaling Works -## Deploy Qdrant +The Horizontal Scaling process consists of the following steps: -```bash -kubectl apply -f https://github.com/kubedb/docs/raw/{{< param "info.version" >}}/docs/examples/qdrant/quickstart/distributed.yaml -kubectl get qdrant -n demo qdrant-sample -w -``` +1. At first, a user creates a `Qdrant` CR. -## Apply HorizontalScaling OpsRequest +2. `KubeDB-Provisioner` operator watches the `Qdrant` CR. -```bash -kubectl apply -f https://github.com/kubedb/docs/raw/{{< param "info.version" >}}/docs/examples/qdrant/scaling/horizontal-scaling/ops-request.yaml -kubectl get qdrantopsrequest -n demo qdrant-horizontal-scale -``` +3. When the operator finds a `Qdrant` CR, it creates a `StatefulSet` with the specified number of node replicas, along with related necessary stuff like secrets, services, etc. -## Verify +4. Then, in order to scale the number of nodes in the `Qdrant` cluster, the user creates a `QdrantOpsRequest` CR with the desired node count. -```bash -kubectl describe qdrantopsrequest -n demo qdrant-horizontal-scale -``` +5. `KubeDB` Ops-manager operator watches the `QdrantOpsRequest` CR. -## Cleaning up +6. When it finds a `QdrantOpsRequest` CR, it pauses the `Qdrant` object so that the `KubeDB-Provisioner` operator doesn't perform any operations on the `Qdrant` during the scaling process. -```bash -kubectl delete qdrantopsrequest -n demo qdrant-horizontal-scale -kubectl delete qdrant -n demo qdrant-sample -kubectl delete ns demo -``` +7. Then the `KubeDB` Ops-manager operator scales the `StatefulSet` to the desired number of replicas. + +8. After the successful scaling of the `StatefulSet`, the `KubeDB` Ops-manager updates the replica count in the `Qdrant` object to reflect the updated state. + +9. After the successful Horizontal Scaling, the `KubeDB` Ops-manager resumes the `Qdrant` object so that the `KubeDB-Provisioner` resumes its usual operations. + +In the next doc, we are going to show a step-by-step guide on Horizontal Scaling of a Qdrant database using `QdrantOpsRequest` CRD. diff --git a/docs/guides/qdrant/scaling/horizontal-scaling/scale-horizontally/index.md b/docs/guides/qdrant/scaling/horizontal-scaling/scale-horizontally/index.md index 50a03408f..f03673ba8 100644 --- a/docs/guides/qdrant/scaling/horizontal-scaling/scale-horizontally/index.md +++ b/docs/guides/qdrant/scaling/horizontal-scaling/scale-horizontally/index.md @@ -12,22 +12,118 @@ section_menu_id: guides > New to KubeDB? Please start [here](/docs/README.md). -# Horizontal Scaling for Qdrant +# Horizontal Scale Qdrant Cluster -This guide shows how to scale Qdrant nodes horizontally using `QdrantOpsRequest`. +This guide will show you how to use `KubeDB` Ops Manager to increase/decrease the number of nodes in a `Qdrant` cluster. ## Before You Begin -- Install KubeDB Community and Enterprise operators. -- Deploy Qdrant in distributed mode. +- At first, you need to have a Kubernetes cluster, and the `kubectl` command-line tool must be configured to communicate with your cluster. If you do not already have a cluster, you can create one by using [kind](https://kind.sigs.k8s.io/docs/user/quick-start/). -## Apply HorizontalScaling OpsRequest +- Install `KubeDB` in your cluster following the steps [here](/docs/setup/README.md). + +- You should be familiar with the following `KubeDB` concepts: + - [Qdrant](/docs/guides/qdrant/concepts/qdrant.md) + - [QdrantOpsRequest](/docs/guides/qdrant/concepts/opsrequest.md) + - [Horizontal Scaling Overview](/docs/guides/qdrant/scaling/horizontal-scaling/overview.md) + +To keep everything isolated, we are going to use a separate namespace called `demo` throughout this tutorial. + +```bash +$ kubectl create ns demo +namespace/demo created +``` + +> **Note:** YAML files used in this tutorial are stored in [docs/guides/qdrant/scaling/horizontal-scaling/scale-horizontally/yamls](/docs/guides/qdrant/scaling/horizontal-scaling/scale-horizontally/yamls) directory of [kubedb/docs](https://github.com/kubedb/docs) repository. + +### Apply Horizontal Scaling on Qdrant Cluster + +Here, we are going to deploy a `Qdrant` cluster using a supported version by `KubeDB` operator. Then we are going to apply horizontal scaling on it. + +#### Prepare Cluster + +At first, we are going to deploy a cluster with 3 nodes. Then, we are going to add two additional nodes through horizontal scaling. Finally, we will remove 1 node from the cluster again via horizontal scaling. + +**Deploy Qdrant Cluster:** + +In this section, we are going to deploy a Qdrant cluster with 3 nodes. Then, in the next section we will scale the cluster using horizontal scaling. Below is the YAML of the `Qdrant` CR that we are going to create: + +```yaml +apiVersion: kubedb.com/v1alpha2 +kind: Qdrant +metadata: + name: qdrant-sample + namespace: demo +spec: + version: "1.17.0" + replicas: 3 + storage: + storageClassName: "standard" + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 1Gi + deletionPolicy: WipeOut +``` + +Let's create the `Qdrant` CR we have shown above: + +```bash +$ kubectl create -f https://github.com/kubedb/docs/raw/{{< param "info.version" >}}/docs/guides/qdrant/scaling/horizontal-scaling/scale-horizontally/yamls/qdrant.yaml +qdrant.kubedb.com/qdrant-sample created +``` + +**Wait for the cluster to be ready:** + +`KubeDB` operator watches for `Qdrant` objects using Kubernetes API. When a `Qdrant` object is created, `KubeDB` operator will create a new PetSet, Services, and Secrets, etc. + +Now, watch `Qdrant` is going to `Running` state and also watch `PetSet` and its pods: + +```bash +$ watch -n 3 kubectl get qdrant -n demo qdrant-sample +Every 3.0s: kubectl get qdrant -n demo qdrant-sample + +NAME VERSION STATUS AGE +qdrant-sample 1.17.0 Ready 4m40m + + +$ watch -n 3 kubectl get petset -n demo qdrant-sample +Every 3.0s: kubectl get petset -n demo qdrant-sample + +NAME READY AGE +qdrant-sample 3/3 4m41m + + +$ watch -n 3 kubectl get pods -n demo +Every 3.0s: kubectl get pod -n demo + +NAME READY STATUS RESTARTS AGE +qdrant-sample-0 1/1 Running 0 4m25m +qdrant-sample-1 1/1 Running 0 4m26m +qdrant-sample-2 1/1 Running 0 4m26m +``` + +Let's check the current number of nodes: + +```bash +$ kubectl get qdrant -n demo qdrant-sample -o=jsonpath='{.spec.replicas}{"\n"}' +3 +``` + +We are ready to apply the `QdrantOpsRequest` CR to scale horizontally. + +#### Scale Up + +Here, we are going to scale up the cluster from 3 nodes to 5 nodes. + +**Create QdrantOpsRequest:** ```yaml apiVersion: ops.kubedb.com/v1alpha1 kind: QdrantOpsRequest metadata: - name: qdrant-horizontal-scale + name: qdops-hscale-up namespace: demo spec: type: HorizontalScaling @@ -37,20 +133,102 @@ spec: node: 5 ``` +Here, + +- `spec.databaseRef.name` specifies that we are performing horizontal scaling on `qdrant-sample` Qdrant database. +- `spec.type` specifies that we are performing `HorizontalScaling` on our database. +- `spec.horizontalScaling.node` specifies the desired number of nodes after scaling. + +Let's create the `QdrantOpsRequest` CR we have shown above: + ```bash -$ kubectl apply -f qdrant-horizontal-scale.yaml -qdrantopsrequest.ops.kubedb.com/qdrant-horizontal-scale created +$ kubectl apply -f https://github.com/kubedb/docs/raw/{{< param "info.version" >}}/docs/guides/qdrant/scaling/horizontal-scaling/scale-horizontally/yamls/hscale-up.yaml +qdrantopsrequest.ops.kubedb.com/qdops-hscale-up created ``` -## Verify +**Verify Qdrant scale-up completed successfully:** ```bash -$ kubectl get qdrantopsrequest -n demo qdrant-horizontal-scale -$ kubectl get pod -n demo -l app.kubernetes.io/instance=qdrant-sample +$ watch -n 3 kubectl get QdrantOpsRequest -n demo qdops-hscale-up +Every 3.0s: kubectl get QdrantOpsRequest -n demo qdops-hscale-up + +NAME TYPE STATUS AGE +qdops-hscale-up HorizontalScaling Successful 3m57s ``` -## Cleaning up +Now let's verify that the number of nodes has increased: + +```bash +$ kubectl get qdrant -n demo qdrant-sample -o=jsonpath='{.spec.replicas}{"\n"}' +5 + +$ kubectl get pods -n demo +NAME READY STATUS RESTARTS AGE +qdrant-sample-0 1/1 Running 0 10m +qdrant-sample-1 1/1 Running 0 10m +qdrant-sample-2 1/1 Running 0 10m +qdrant-sample-3 1/1 Running 0 2m +qdrant-sample-4 1/1 Running 0 1m +``` + +#### Scale Down + +Here, we are going to scale down the cluster from 5 nodes to 4 nodes. + +**Create QdrantOpsRequest:** + +```yaml +apiVersion: ops.kubedb.com/v1alpha1 +kind: QdrantOpsRequest +metadata: + name: qdops-hscale-down + namespace: demo +spec: + type: HorizontalScaling + databaseRef: + name: qdrant-sample + horizontalScaling: + node: 4 +``` + +Let's create the `QdrantOpsRequest` CR we have shown above: + +```bash +$ kubectl apply -f https://github.com/kubedb/docs/raw/{{< param "info.version" >}}/docs/guides/qdrant/scaling/horizontal-scaling/scale-horizontally/yamls/hscale-down.yaml +qdrantopsrequest.ops.kubedb.com/qdops-hscale-down created +``` + +**Verify Qdrant scale-down completed successfully:** + +```bash +$ watch -n 3 kubectl get QdrantOpsRequest -n demo qdops-hscale-down +Every 3.0s: kubectl get QdrantOpsRequest -n demo qdops-hscale-down + +NAME TYPE STATUS AGE +qdops-hscale-down HorizontalScaling Successful 2m15s +``` + +Now let's verify that the number of nodes has decreased: + +```bash +$ kubectl get qdrant -n demo qdrant-sample -o=jsonpath='{.spec.replicas}{"\n"}' +4 + +$ kubectl get pods -n demo +NAME READY STATUS RESTARTS AGE +qdrant-sample-0 1/1 Running 0 14m +qdrant-sample-1 1/1 Running 0 14m +qdrant-sample-2 1/1 Running 0 14m +qdrant-sample-3 1/1 Running 0 6m +``` + +We have successfully performed horizontal scaling on the Qdrant cluster. + +## Cleaning Up + +To clean up the Kubernetes resources created by this tutorial, run: ```bash -kubectl delete qdrantopsrequest -n demo qdrant-horizontal-scale +kubectl delete qdrant -n demo qdrant-sample +kubectl delete QdrantOpsRequest -n demo qdops-hscale-up qdops-hscale-down ``` \ No newline at end of file diff --git a/docs/guides/qdrant/scaling/vertical-scaling/overview.md b/docs/guides/qdrant/scaling/vertical-scaling/overview.md index a22c83110..b3fea6747 100644 --- a/docs/guides/qdrant/scaling/vertical-scaling/overview.md +++ b/docs/guides/qdrant/scaling/vertical-scaling/overview.md @@ -14,41 +14,34 @@ section_menu_id: guides # Qdrant Vertical Scaling -This guide shows how to update CPU and memory resources of Qdrant nodes. +This guide will give an overview of how KubeDB Ops-manager updates the CPU and memory resources of `Qdrant` database nodes. ## Before You Begin -- Ensure database is healthy and all pods are running. -- Use the example files from `docs/examples/qdrant/quickstart/distributed.yaml` and `docs/examples/qdrant/scaling/vertical-scaling/ops-request.yaml`. +- You should be familiar with the following `KubeDB` concepts: + - [Qdrant](/docs/guides/qdrant/concepts/qdrant.md) + - [QdrantOpsRequest](/docs/guides/qdrant/concepts/opsrequest.md) -```bash -kubectl create ns demo -``` +## How Vertical Scaling Works -## Deploy Qdrant +The Vertical Scaling process consists of the following steps: -```bash -kubectl apply -f https://github.com/kubedb/docs/raw/{{< param "info.version" >}}/docs/examples/qdrant/quickstart/distributed.yaml -kubectl get qdrant -n demo qdrant-sample -w -``` +1. At first, a user creates a `Qdrant` CR. -## Apply VerticalScaling OpsRequest +2. `KubeDB-Provisioner` operator watches the `Qdrant` CR. -```bash -kubectl apply -f https://github.com/kubedb/docs/raw/{{< param "info.version" >}}/docs/examples/qdrant/scaling/vertical-scaling/ops-request.yaml -kubectl get qdrantopsrequest -n demo qdrant-vertical-scale -``` +3. When the operator finds a `Qdrant` CR, it creates a `StatefulSet` and related necessary stuff like secrets, services, etc. -## Verify +4. Then, in order to update the CPU and memory resources of the `Qdrant` database nodes, the user creates a `QdrantOpsRequest` CR with the desired resource specifications. -```bash -kubectl describe qdrantopsrequest -n demo qdrant-vertical-scale -``` +5. `KubeDB` Ops-manager operator watches the `QdrantOpsRequest` CR. -## Cleaning up +6. When it finds a `QdrantOpsRequest` CR, it pauses the `Qdrant` object so that the `KubeDB-Provisioner` operator doesn't perform any operations on the `Qdrant` during the scaling process. -```bash -kubectl delete qdrantopsrequest -n demo qdrant-vertical-scale -kubectl delete qdrant -n demo qdrant-sample -kubectl delete ns demo -``` +7. Then the `KubeDB` Ops-manager operator updates the resources of the `StatefulSet` pods to the desired values defined in the `QdrantOpsRequest` CR. + +8. After the successful resource update of the pods, the `KubeDB` Ops-manager updates the resource specifications in the `Qdrant` object to reflect the updated state. + +9. After the successful Vertical Scaling, the `KubeDB` Ops-manager resumes the `Qdrant` object so that the `KubeDB-Provisioner` resumes its usual operations. + +In the next doc, we are going to show a step-by-step guide on Vertical Scaling of a Qdrant database using `QdrantOpsRequest` CRD. diff --git a/docs/guides/qdrant/scaling/vertical-scaling/scale-vertically/index.md b/docs/guides/qdrant/scaling/vertical-scaling/scale-vertically/index.md index 18b06c1cb..7def4d69c 100644 --- a/docs/guides/qdrant/scaling/vertical-scaling/scale-vertically/index.md +++ b/docs/guides/qdrant/scaling/vertical-scaling/scale-vertically/index.md @@ -12,22 +12,114 @@ section_menu_id: guides > New to KubeDB? Please start [here](/docs/README.md). -# Vertical Scaling for Qdrant +# Vertical Scale Qdrant Cluster -This guide shows how to change CPU and memory resources of Qdrant nodes using `QdrantOpsRequest`. +This guide will show you how to use `KubeDB` Ops Manager to update the resources of a `Qdrant` instance. ## Before You Begin -- Install KubeDB Community and Enterprise operators. -- Ensure database is healthy before applying scaling changes. +- At first, you need to have a Kubernetes cluster, and the `kubectl` command-line tool must be configured to communicate with your cluster. If you do not already have a cluster, you can create one by using [kind](https://kind.sigs.k8s.io/docs/user/quick-start/). -## Apply VerticalScaling OpsRequest +- Install `KubeDB` Provisioner and Ops-manager operator in your cluster following the steps [here](/docs/setup/README.md). + +- You should be familiar with the following `KubeDB` concepts: + - [Qdrant](/docs/guides/qdrant/concepts/qdrant.md) + - [QdrantOpsRequest](/docs/guides/qdrant/concepts/opsrequest.md) + - [Vertical Scaling Overview](/docs/guides/qdrant/scaling/vertical-scaling/overview.md) + +To keep everything isolated, we are going to use a separate namespace called `demo` throughout this tutorial. + +```bash +$ kubectl create ns demo +namespace/demo created +``` + +> **Note:** YAML files used in this tutorial are stored in [docs/guides/qdrant/scaling/vertical-scaling/scale-vertically/yamls](/docs/guides/qdrant/scaling/vertical-scaling/scale-vertically/yamls) directory of [kubedb/docs](https://github.com/kubedb/docs) repository. + +### Apply Vertical Scaling on Qdrant Cluster + +Here, we are going to deploy a `Qdrant` cluster using a supported version by `KubeDB` operator. Then we are going to apply vertical scaling on it. + +**Deploy Qdrant:** + +In this section, we are going to deploy a Qdrant cluster. Then, in the next section, we will update the resources of the database nodes using vertical scaling. Below is the YAML of the `Qdrant` CR that we are going to create: + +```yaml +apiVersion: kubedb.com/v1alpha2 +kind: Qdrant +metadata: + name: qdrant-sample + namespace: demo +spec: + version: "1.17.0" + replicas: 3 + storage: + storageClassName: "standard" + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 1Gi + deletionPolicy: WipeOut +``` + +Let's create the `Qdrant` CR we have shown above: + +```bash +$ kubectl create -f https://github.com/kubedb/docs/raw/{{< param "info.version" >}}/docs/guides/qdrant/scaling/vertical-scaling/scale-vertically/yamls/qdrant.yaml +qdrant.kubedb.com/qdrant-sample created +``` + +**Wait for the cluster to be ready:** + +```bash +$ watch -n 3 kubectl get qdrant -n demo qdrant-sample +Every 3.0s: kubectl get qdrant -n demo qdrant-sample + +NAME VERSION STATUS AGE +qdrant-sample 1.17.0 Ready 3m16s + +$ watch -n 3 kubectl get petset -n demo qdrant-sample +Every 3.0s: kubectl get petset -n demo qdrant-sample + +NAME READY AGE +qdrant-sample 3/3 3m54s + +$ watch -n 3 kubectl get pod -n demo +Every 3.0s: kubectl get pod -n demo + +NAME READY STATUS RESTARTS AGE +qdrant-sample-0 1/1 Running 0 4m51s +qdrant-sample-1 1/1 Running 0 3m50s +qdrant-sample-2 1/1 Running 0 3m46s +``` + +Let's check the resources of the `qdrant-sample-0` pod: + +```bash +$ kubectl get pod -n demo qdrant-sample-0 -o json | jq '.spec.containers[0].resources' +{ + "limits": { + "memory": "1Gi" + }, + "requests": { + "cpu": "250m", + "memory": "512Mi" + } +} +``` + +We are ready to apply the `QdrantOpsRequest` CR to vertically scale the cluster. + +#### Create QdrantOpsRequest for Vertical Scaling + +In order to update the resources of the database, we have to create a `QdrantOpsRequest` CR with our desired resources. Below is the YAML of the `QdrantOpsRequest` CR that we are going to create: ```yaml apiVersion: ops.kubedb.com/v1alpha1 kind: QdrantOpsRequest metadata: - name: qdrant-vertical-scale + name: qdops-vscale namespace: demo spec: type: VerticalScaling @@ -44,14 +136,56 @@ spec: memory: "2Gi" ``` +Here, + +- `spec.databaseRef.name` specifies that we are performing vertical scaling on `qdrant-sample` Qdrant database. +- `spec.type` specifies that we are performing `VerticalScaling` on our database. +- `spec.verticalScaling.node.resources` specifies the desired CPU and memory resources for the Qdrant nodes. + +Let's create the `QdrantOpsRequest` CR we have shown above: + +```bash +$ kubectl apply -f https://github.com/kubedb/docs/raw/{{< param "info.version" >}}/docs/guides/qdrant/scaling/vertical-scaling/scale-vertically/yamls/vscale.yaml +qdrantopsrequest.ops.kubedb.com/qdops-vscale created +``` + +#### Verify Qdrant vertical scaling completed successfully + +If everything goes well, `KubeDB` Ops-manager operator will update the resources of the `Qdrant` object and related `PetSet`. + +Let's wait for `QdrantOpsRequest` to be `Successful`: + ```bash -$ kubectl apply -f qdrant-vertical-scale.yaml -qdrantopsrequest.ops.kubedb.com/qdrant-vertical-scale created +$ watch -n 3 kubectl get QdrantOpsRequest -n demo qdops-vscale +Every 3.0s: kubectl get QdrantOpsRequest -n demo qdops-vscale + +NAME TYPE STATUS AGE +qdops-vscale VerticalScaling Successful 3m12s ``` -## Verify +Now, let's verify that the resources of the pods have been updated: + +```bash +$ kubectl get pod -n demo qdrant-sample-0 -o json | jq '.spec.containers[0].resources' +{ + "limits": { + "cpu": "1", + "memory": "2Gi" + }, + "requests": { + "cpu": "500m", + "memory": "1Gi" + } +} +``` + +You can see from the above output that the resources of the `qdrant-sample-0` pod have been updated successfully. All pods in the cluster will have the same updated resource configuration. + +## Cleaning Up + +To clean up the Kubernetes resources created by this tutorial, run: ```bash -$ kubectl get qdrantopsrequest -n demo qdrant-vertical-scale -$ kubectl describe qdrantopsrequest -n demo qdrant-vertical-scale +kubectl delete qdrant -n demo qdrant-sample +kubectl delete QdrantOpsRequest -n demo qdops-vscale ``` \ No newline at end of file diff --git a/docs/guides/qdrant/tls/configure/index.md b/docs/guides/qdrant/tls/configure/index.md index a275556cb..29d556408 100644 --- a/docs/guides/qdrant/tls/configure/index.md +++ b/docs/guides/qdrant/tls/configure/index.md @@ -12,59 +12,161 @@ section_menu_id: guides > New to KubeDB? Please start [here](/docs/README.md). -# Configure TLS in Qdrant +# Configure TLS/SSL in Qdrant -This guide shows how to configure TLS for both client and peer-to-peer traffic in Qdrant. +`KubeDB` provides support for TLS/SSL encryption for `Qdrant`. This tutorial will show you how to use `KubeDB` to deploy a `Qdrant` database with TLS/SSL configuration. ## Before You Begin -- Install KubeDB operator. -- Install cert-manager and create an `Issuer` or `ClusterIssuer`. +- At first, you need to have a Kubernetes cluster, and the `kubectl` command-line tool must be configured to communicate with your cluster. If you do not already have a cluster, you can create one by using [kind](https://kind.sigs.k8s.io/docs/user/quick-start/). + +- Install [`cert-manager`](https://cert-manager.io/docs/installation/) v1.4.0 or later to your cluster to manage your SSL/TLS certificates. + +- Install `KubeDB` in your cluster following the steps [here](/docs/setup/README.md). + +- You should be familiar with the following `KubeDB` concepts: + - [Qdrant](/docs/guides/qdrant/concepts/qdrant.md) + - [TLS Overview](/docs/guides/qdrant/tls/overview.md) + +To keep things isolated, this tutorial uses a separate namespace called `demo` throughout this tutorial. ```bash $ kubectl create ns demo namespace/demo created ``` -## Deploy Qdrant with TLS +> **Note:** YAML files used in this tutorial are stored in [docs/guides/qdrant/tls/configure/yamls](/docs/guides/qdrant/tls/configure/yamls) directory of [kubedb/docs](https://github.com/kubedb/docs) repository. + +### Deploy Qdrant database with TLS/SSL configuration + +As a pre-requisite, we are going to create an Issuer/ClusterIssuer. This Issuer/ClusterIssuer is used to create certificates. Then we are going to deploy a Qdrant with TLS/SSL configuration. + +### Create Issuer/ClusterIssuer + +Now, we are going to create an example `Issuer` that will be used throughout the duration of this tutorial. Alternatively, you can follow this [cert-manager tutorial](https://cert-manager.io/docs/configuration/ca/) to create your own `Issuer`. By following the below steps, we are going to create our desired issuer, + +- Start off by generating our ca-certificates using openssl: + +```bash +openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout ./ca.key -out ./ca.crt -subj "/CN=qdrant/O=kubedb" +``` + +- Create a secret using the certificate files we have just generated: + +```bash +$ kubectl create secret tls qdrant-ca --cert=ca.crt --key=ca.key --namespace=demo +secret/qdrant-ca created +``` + +Now, we are going to create an `Issuer` using the `qdrant-ca` secret that contains the CA certificate we have just created. Below is the YAML of the `Issuer` CR that we are going to create: + +```yaml +apiVersion: cert-manager.io/v1 +kind: Issuer +metadata: + name: qdrant-ca-issuer + namespace: demo +spec: + ca: + secretName: qdrant-ca +``` + +Let's create the `Issuer` CR we have shown above: + +```bash +$ kubectl apply -f https://github.com/kubedb/docs/raw/{{< param "info.version" >}}/docs/guides/qdrant/tls/configure/yamls/issuer.yaml +issuer.cert-manager.io/qdrant-ca-issuer created +``` + +### Deploy Qdrant cluster with TLS/SSL configuration + +Here, our issuer `qdrant-ca-issuer` is ready to deploy a `Qdrant` cluster with TLS/SSL configuration. Below is the YAML for the Qdrant cluster that we are going to create: ```yaml apiVersion: kubedb.com/v1alpha2 kind: Qdrant metadata: - name: tls-qdrant + name: qdrant-tls namespace: demo spec: - version: 1.17.0 - mode: Distributed + version: "1.17.0" replicas: 3 - storageType: Durable - storage: - accessModes: - - ReadWriteOnce - resources: - requests: - storage: 2Gi tls: issuerRef: apiGroup: cert-manager.io + name: qdrant-ca-issuer kind: Issuer - name: qdrant-issuer - client: true - p2p: true + certificates: + - alias: server + subject: + organizations: + - kubedb:server + dnsNames: + - localhost + ipAddresses: + - "127.0.0.1" + storage: + storageClassName: "standard" + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 1Gi deletionPolicy: WipeOut ``` -## Verify +Here, + +- `spec.tls.issuerRef` refers to the `qdrant-ca-issuer` issuer. +- `spec.tls.certificates` provides options to configure certificate renewal and keep-alive. You can find more details from [here](/docs/guides/qdrant/concepts/qdrant.md#tls). + +**Deploy Qdrant Cluster:** + +Let's create the `Qdrant` CR we have shown above: ```bash -$ kubectl get qdrant -n demo tls-qdrant -$ kubectl get secret -n demo | grep tls-qdrant +$ kubectl apply -f https://github.com/kubedb/docs/raw/{{< param "info.version" >}}/docs/guides/qdrant/tls/configure/yamls/tls-qdrant.yaml +qdrant.kubedb.com/qdrant-tls created ``` +**Wait for the database to be ready:** + +Now, watch `Qdrant` going to `Running` state and also watch `PetSet` and its pods going to `Running` state: + +```bash +$ watch kubectl get qdrant -n demo qdrant-tls +NAME VERSION STATUS AGE +qdrant-tls 1.17.0 Ready 62s + +$ watch -n 3 kubectl get petset -n demo qdrant-tls +NAME READY AGE +qdrant-tls 3/3 2m30s + +$ watch -n 3 kubectl get pod -n demo -l app.kubernetes.io/instance=qdrant-tls +NAME READY STATUS RESTARTS AGE +qdrant-tls-0 1/1 Running 0 3m5s +qdrant-tls-1 1/1 Running 0 2m40s +qdrant-tls-2 1/1 Running 0 2m20s +``` + +**Verify TLS/SSL configuration:** + +Now, let's verify the TLS/SSL configuration by checking the secrets created for the Qdrant database: + +```bash +$ kubectl get secrets -n demo | grep qdrant-tls +qdrant-tls-server-cert kubernetes.io/tls 3 3m +qdrant-tls-client-cert kubernetes.io/tls 3 3m +``` + +The TLS certificates have been created and the Qdrant cluster is now configured to use TLS/SSL for both client connections and peer-to-peer communication. + ## Cleaning up +To clean up the Kubernetes resources created by this tutorial, run: + ```bash -kubectl delete qdrant -n demo tls-qdrant +kubectl delete qdrant -n demo qdrant-tls +kubectl delete issuer -n demo qdrant-ca-issuer kubectl delete ns demo ``` \ No newline at end of file diff --git a/docs/guides/qdrant/tls/overview.md b/docs/guides/qdrant/tls/overview.md index 6afca780c..c6d6ff4e8 100644 --- a/docs/guides/qdrant/tls/overview.md +++ b/docs/guides/qdrant/tls/overview.md @@ -10,30 +10,40 @@ menu_name: docs_{{ .version }} section_menu_id: guides --- -# Qdrant TLS +> New to KubeDB? Please start [here](/docs/README.md). -This guide shows the key TLS considerations for Qdrant. +# Qdrant TLS/SSL Encryption + +This guide will give an overview of how KubeDB supports TLS/SSL encryption for `Qdrant` databases. ## Before You Begin -- Install `cert-manager` in your cluster. -- Deploy Qdrant first using the [quickstart guide](/docs/guides/qdrant/quickstart/quickstart.md). +- You should be familiar with the following `KubeDB` concepts: + - [Qdrant](/docs/guides/qdrant/concepts/qdrant.md) + - [QdrantOpsRequest](/docs/guides/qdrant/concepts/opsrequest.md) + +## How TLS Works for Qdrant + +KubeDB uses `cert-manager` to manage TLS certificates for Qdrant databases. The TLS configuration process consists of the following steps: + +1. At first, a user creates a `ClusterIssuer` or `Issuer` using `cert-manager`. + +2. The user then creates a `Qdrant` CR with the `spec.tls` field configured, pointing to the `Issuer` or `ClusterIssuer`. + +3. `KubeDB-Provisioner` operator watches the `Qdrant` CR. -## Configure TLS +4. When the operator finds a `Qdrant` CR with `spec.tls` configured, it requests TLS certificates from `cert-manager` using the specified issuer. -Qdrant TLS configuration is available via `spec.tls`. +5. `cert-manager` creates the certificates and stores them in a `Secret`. -- Enable client and p2p certificate handling. -- Manage cert issuance using cert-manager issuer references. +6. `KubeDB-Provisioner` operator creates the `StatefulSet` with the TLS secrets mounted, enabling encrypted communication. -## Verify +7. The `Qdrant` database nodes use these certificates for encrypted client-to-server and peer-to-peer communication. -```bash -kubectl get qdrant -n demo qdrant-sample -o yaml -kubectl get secret -n demo -``` +KubeDB supports the following TLS configurations for Qdrant: -## Next Steps +- **Add TLS** — Enable TLS on an existing non-TLS Qdrant database using a `QdrantOpsRequest`. +- **Rotate TLS** — Rotate the existing TLS certificates to refresh expiring certificates. +- **Remove TLS** — Remove TLS from an existing TLS-enabled Qdrant database. -- Test both client and peer communication after enabling TLS. -- Use the dedicated [Reconfigure TLS guide](/docs/guides/qdrant/reconfigure-tls/overview.md) for certificate rotation. +In the next doc, we are going to show a step-by-step guide on configuring TLS for a Qdrant database. diff --git a/docs/guides/qdrant/update-version/overview.md b/docs/guides/qdrant/update-version/overview.md index 0c31c3550..bbcd252c0 100644 --- a/docs/guides/qdrant/update-version/overview.md +++ b/docs/guides/qdrant/update-version/overview.md @@ -14,42 +14,34 @@ section_menu_id: guides # Updating Qdrant Version -This guide shows how to update Qdrant to a supported target version. +This guide will give you an overview of how KubeDB Ops-manager updates the version of a `Qdrant` database. ## Before You Begin -- Ensure Qdrant is `Ready` before submitting the update request. -- Use the example files from `docs/examples/qdrant/quickstart/distributed.yaml` and `docs/examples/qdrant/update-version/ops-request.yaml`. +- You should be familiar with the following `KubeDB` concepts: + - [Qdrant](/docs/guides/qdrant/concepts/qdrant.md) + - [QdrantOpsRequest](/docs/guides/qdrant/concepts/opsrequest.md) -```bash -kubectl create ns demo -kubectl get qdrantversions -``` +## How the Update Process Works -## Deploy Qdrant +The updating process consists of the following steps: -```bash -kubectl apply -f https://github.com/kubedb/docs/raw/{{< param "info.version" >}}/docs/examples/qdrant/quickstart/distributed.yaml -kubectl get qdrant -n demo qdrant-sample -w -``` +1. At first, a user creates a `Qdrant` CR. -## Apply UpdateVersion OpsRequest +2. `KubeDB-Provisioner` operator watches for the `Qdrant` CR. -```bash -kubectl apply -f https://github.com/kubedb/docs/raw/{{< param "info.version" >}}/docs/examples/qdrant/update-version/ops-request.yaml -kubectl get qdrantopsrequest -n demo qdrant-update-version -``` +3. When it finds one, it creates a `StatefulSet` and related necessary stuff like secrets, services, etc. -## Verify +4. Then, in order to update the version of the `Qdrant` database, the user creates a `QdrantOpsRequest` CR with the desired target version. -```bash -kubectl describe qdrantopsrequest -n demo qdrant-update-version -``` +5. `KubeDB-ops-manager` operator watches for `QdrantOpsRequest`. -## Cleaning up +6. When it finds one, it pauses the `Qdrant` object so that the `KubeDB-Provisioner` operator doesn't perform any operations on the `Qdrant` during the updating process. -```bash -kubectl delete qdrantopsrequest -n demo qdrant-update-version -kubectl delete qdrant -n demo qdrant-sample -kubectl delete ns demo -``` +7. By looking at the target version from the `QdrantOpsRequest` CR, the `KubeDB-ops-manager` operator updates the images of the `StatefulSet` for the new version. + +8. After successful update of the `StatefulSet` and its Pod images, the `KubeDB-ops-manager` updates the image of the `Qdrant` object to reflect the updated cluster state. + +9. After successful update of the `Qdrant` object, the `KubeDB` Ops-manager resumes the `Qdrant` object so that the `KubeDB-Provisioner` can resume its usual operations. + +In the next doc, we are going to show a step-by-step guide on updating a Qdrant database using the `UpdateVersion` operation. diff --git a/docs/guides/qdrant/update-version/versionupgrading/index.md b/docs/guides/qdrant/update-version/versionupgrading/index.md index 4c068a07a..c6341896d 100644 --- a/docs/guides/qdrant/update-version/versionupgrading/index.md +++ b/docs/guides/qdrant/update-version/versionupgrading/index.md @@ -1,5 +1,5 @@ --- -title: Upgrade Qdrant Version +title: Update Qdrant Version menu: docs_{{ .version }}: identifier: qdrant-version-upgrading @@ -12,43 +12,281 @@ section_menu_id: guides > New to KubeDB? Please start [here](/docs/README.md). -# Upgrade Qdrant Version +# Update Qdrant Version -This guide shows how to upgrade Qdrant version using `QdrantOpsRequest`. +This guide will show you how to use `KubeDB` ops-manager operator to update the version of `Qdrant` cr. ## Before You Begin -- Ensure target version exists in `QdrantVersion` catalog. -- Ensure the database is in `Ready` state. +- At first, you need to have a Kubernetes cluster, and the `kubectl` command-line tool must be configured to communicate with your cluster. If you do not already have a cluster, you can create one by using [kind](https://kind.sigs.k8s.io/docs/user/quick-start/). + +- Install `KubeDB` in your cluster following the steps [here](/docs/setup/README.md). + +- You should be familiar with the following `KubeDB` concepts: + - [Qdrant](/docs/guides/qdrant/concepts/qdrant.md) + - [QdrantOpsRequest](/docs/guides/qdrant/concepts/opsrequest.md) + - [Updating Overview](/docs/guides/qdrant/update-version/overview/index.md) + +To keep everything isolated, we are going to use a separate namespace called `demo` throughout this tutorial. ```bash -$ kubectl get qdrantversions +$ kubectl create ns demo +namespace/demo created ``` -## Apply UpdateVersion OpsRequest +> **Note:** YAML files used in this tutorial are stored in [docs/guides/qdrant/update-version/versionupgrading/yamls](/docs/guides/qdrant/update-version/versionupgrading/yamls) directory of [kubedb/docs](https://github.com/kubedb/docs) repository. + +### Apply Version Updating on Qdrant + +Here, we are going to deploy a `Qdrant` instance using a supported version by `KubeDB` provisioner. Then we are going to apply update-ops-request on it. + +#### Prepare Qdrant + +At first, we are going to deploy a Qdrant instance using a supported `Qdrant` version. In the next two sections, we are going to find out the supported versions and version update constraints. + +**Find supported QdrantVersion:** + +When you have installed `KubeDB`, it has created `QdrantVersion` CR for all supported `Qdrant` versions. Let's check the supported versions: + +```bash +$ kubectl get qdrantversion +NAME VERSION DB_IMAGE DEPRECATED AGE +1.7.4 1.7.4 qdrant/qdrant:v1.7.4 5m +1.8.4 1.8.4 qdrant/qdrant:v1.8.4 5m +1.9.7 1.9.7 qdrant/qdrant:v1.9.7 5m +1.10.1 1.10.1 qdrant/qdrant:v1.10.1 5m +1.11.5 1.11.5 qdrant/qdrant:v1.11.5 5m +1.12.6 1.12.6 qdrant/qdrant:v1.12.6 5m +1.13.4 1.13.4 qdrant/qdrant:v1.13.4 5m +1.14.1 1.14.1 qdrant/qdrant:v1.14.1 5m +1.15.1 1.15.1 qdrant/qdrant:v1.15.1 5m +1.16.1 1.16.1 qdrant/qdrant:v1.16.1 5m +1.17.0 1.17.0 qdrant/qdrant:v1.17.0 5m +1.18.0 1.18.0 qdrant/qdrant:v1.18.0 5m +``` + +The version above that does not show `DEPRECATED` `true` is supported by `KubeDB` for `Qdrant`. You can use any non-deprecated version. Now, we are going to select a non-deprecated version from `QdrantVersion` for the `Qdrant` instance that we will update from this version to another. In the next section, we are going to verify version update constraints. + +**Check update Constraints:** + +Qdrant supports rolling version updates. You can update from any currently running version to a newer patch or minor version. Major version jumps should follow the Qdrant upstream upgrade notes. For example, you can update directly from `1.17.0` to `1.18.0`. + +Let's get one of the `qdrantversion` YAMLs: + +```bash +$ kubectl get qdrantversion 1.17.0 -o yaml | kubectl neat +apiVersion: catalog.kubedb.com/v1alpha1 +kind: QdrantVersion +metadata: + labels: + app.kubernetes.io/instance: kubedb + app.kubernetes.io/managed-by: Helm + app.kubernetes.io/name: kubedb-catalog + name: "1.17.0" +spec: + db: + image: qdrant/qdrant:v1.17.0 + version: "1.17.0" +``` + +**Deploy Qdrant Instance:** + +In this section, we are going to deploy a Qdrant instance. Then, in the next section, we will update the version of the database using `UpdateVersion`. Below is the YAML of the `Qdrant` cr that we are going to create: + +```yaml +apiVersion: kubedb.com/v1alpha2 +kind: Qdrant +metadata: + name: qdrant-sample + namespace: demo +spec: + version: "1.17.0" + replicas: 3 + storage: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 1Gi + deletionPolicy: WipeOut +``` + +Let's create the `Qdrant` cr we have shown above: + +```bash +$ kubectl create -f https://github.com/kubedb/docs/raw/{{< param "info.version" >}}/docs/guides/qdrant/update-version/versionupgrading/yamls/qdrant.yaml +qdrant.kubedb.com/qdrant-sample created +``` + +**Wait for the database to be ready:** + +`KubeDB` operator watches for `Qdrant` objects using Kubernetes API. When a `Qdrant` object is created, `KubeDB` operator will create a new PetSet, Services, and Secrets, etc. + +Now, watch `Qdrant` is going to `Running` state and also watch `PetSet` and its pod is created and going to `Running` state: + +```bash +$ watch -n 3 kubectl get qdrant -n demo +Every 3.0s: kubectl get qdrant -n demo + +NAME VERSION STATUS AGE +qdrant-sample 1.17.0 Ready 3m42s + +$ watch -n 3 kubectl get petset -n demo qdrant-sample +Every 3.0s: kubectl get petset -n demo qdrant-sample + +NAME READY AGE +qdrant-sample 3/3 4m17s + +$ watch -n 3 kubectl get pod -n demo +Every 3.0s: kubectl get pods -n demo + +NAME READY STATUS RESTARTS AGE +qdrant-sample-0 1/1 Running 0 4m55s +qdrant-sample-1 1/1 Running 0 4m12s +qdrant-sample-2 1/1 Running 0 3m38s +``` + +Let's verify the `Qdrant`, the `PetSet` and its `Pod` image version: + +```bash +$ kubectl get qdrant -n demo qdrant-sample -o=jsonpath='{.spec.version}{"\n"}' +1.17.0 + +$ kubectl get petset -n demo qdrant-sample -o=jsonpath='{.spec.template.spec.containers[0].image}{"\n"}' +qdrant/qdrant:v1.17.0 + +$ kubectl get pod -n demo qdrant-sample-0 -o=jsonpath='{.spec.containers[0].image}{"\n"}' +qdrant/qdrant:v1.17.0 +``` + +We are ready to apply version updating on this `Qdrant` instance. + +#### UpdateVersion + +Here, we are going to update `Qdrant` from version `1.17.0` to `1.18.0`. + +**Create QdrantOpsRequest:** + +To update the instance, you have to create a `QdrantOpsRequest` cr with your desired version that is supported by `KubeDB`. Below is the YAML of the `QdrantOpsRequest` cr that we are going to create: ```yaml apiVersion: ops.kubedb.com/v1alpha1 kind: QdrantOpsRequest metadata: - name: qdrant-version-upgrade + name: qdops-update-version namespace: demo spec: type: UpdateVersion - databaseRef: - name: qdrant-sample updateVersion: targetVersion: "1.18.0" + databaseRef: + name: qdrant-sample +``` + +Here, + +- `spec.databaseRef.name` specifies that we are performing operation on `qdrant-sample` Qdrant database. +- `spec.type` specifies that we are going to perform `UpdateVersion` on our database. +- `spec.updateVersion.targetVersion` specifies the expected version `1.18.0` after updating. + +Let's create the `QdrantOpsRequest` cr we have shown above: + +```bash +$ kubectl apply -f https://github.com/kubedb/docs/raw/{{< param "info.version" >}}/docs/guides/qdrant/update-version/versionupgrading/yamls/update_version.yaml +qdrantopsrequest.ops.kubedb.com/qdops-update-version created +``` + +**Verify Qdrant version updated successfully:** + +If everything goes well, `KubeDB` ops-manager operator will update the image of `Qdrant`, `PetSet`, and its `Pod`. + +At first, we will wait for `QdrantOpsRequest` to be successful. Run the following command to watch `QdrantOpsRequest` cr: + +```bash +$ watch -n 3 kubectl get QdrantOpsRequest -n demo qdops-update-version +Every 3.0s: kubectl get QdrantOpsRequest -n demo qdops-update-version + +NAME TYPE STATUS AGE +qdops-update-version UpdateVersion Successful 3m12s +``` + +We can see from the above output that the `QdrantOpsRequest` has succeeded. If we describe the `QdrantOpsRequest`, we shall see that the `Qdrant`, `PetSet`, and its `Pod` have updated with a new image. + +```bash +$ kubectl describe QdrantOpsRequest -n demo qdops-update-version +Name: qdops-update-version +Namespace: demo +Labels: +Annotations: +API Version: ops.kubedb.com/v1alpha1 +Kind: QdrantOpsRequest +Spec: + Database Ref: + Name: qdrant-sample + Type: UpdateVersion + Update Version: + Target Version: 1.18.0 +Status: + Conditions: + Last Transition Time: 2026-05-01T10:00:00Z + Message: Qdrant ops request is updating version + Observed Generation: 1 + Reason: UpdateVersion + Status: True + Type: UpdateVersion + Last Transition Time: 2026-05-01T10:00:05Z + Message: Successfully updated PetSets update strategy type + Observed Generation: 1 + Reason: UpdatePetSets + Status: True + Type: UpdatePetSets + Last Transition Time: 2026-05-01T10:00:30Z + Message: Successfully updated pod images + Observed Generation: 1 + Reason: UpdatePetSetImage + Status: True + Type: UpdatePetSetImage + Last Transition Time: 2026-05-01T10:02:45Z + Message: Successfully completed the modification process. + Observed Generation: 1 + Reason: Successful + Status: True + Type: Successful + Observed Generation: 1 + Phase: Successful +Events: + Type Reason Age From Message + ---- ------ ---- ---- ------- + Normal PauseDatabase 3m12s KubeDB Enterprise Operator Pausing Qdrant demo/qdrant-sample + Normal PauseDatabase 3m12s KubeDB Enterprise Operator Successfully paused Qdrant demo/qdrant-sample + Normal Updating 3m12s KubeDB Enterprise Operator Updating PetSets + Normal Updating 3m12s KubeDB Enterprise Operator Successfully Updated PetSets + Normal UpdatePetSetImage 1m10s KubeDB Enterprise Operator Successfully Updated pod images + Normal ResumeDatabase 1m10s KubeDB Enterprise Operator Resuming Qdrant demo/qdrant-sample + Normal ResumeDatabase 1m10s KubeDB Enterprise Operator Successfully resumed Qdrant demo/qdrant-sample + Normal Successful 1m10s KubeDB Enterprise Operator Successfully Updated Database ``` +Now, we are going to verify whether the `Qdrant`, `PetSet` and its `Pod` have updated with the new image. Let's check: + ```bash -$ kubectl apply -f qdrant-version-upgrade.yaml -qdrantopsrequest.ops.kubedb.com/qdrant-version-upgrade created +$ kubectl get qdrant -n demo qdrant-sample -o=jsonpath='{.spec.version}{"\n"}' +1.18.0 + +$ kubectl get petset -n demo qdrant-sample -o=jsonpath='{.spec.template.spec.containers[0].image}{"\n"}' +qdrant/qdrant:v1.18.0 + +$ kubectl get pod -n demo qdrant-sample-0 -o=jsonpath='{.spec.containers[0].image}{"\n"}' +qdrant/qdrant:v1.18.0 ``` -## Verify +You can see above that our `Qdrant` has been updated with the new version. It verifies that we have successfully updated our Qdrant instance. + +## Cleaning Up + +To clean up the Kubernetes resources created by this tutorial, run: ```bash -$ kubectl get qdrantopsrequest -n demo qdrant-version-upgrade -$ kubectl get qdrant -n demo qdrant-sample +kubectl delete qdrant -n demo qdrant-sample +kubectl delete QdrantOpsRequest -n demo qdops-update-version ``` \ No newline at end of file diff --git a/docs/guides/qdrant/volume-expansion/overview.md b/docs/guides/qdrant/volume-expansion/overview.md index 1b581aacc..ce719e74c 100644 --- a/docs/guides/qdrant/volume-expansion/overview.md +++ b/docs/guides/qdrant/volume-expansion/overview.md @@ -12,44 +12,38 @@ section_menu_id: guides > New to KubeDB? Please start [here](/docs/README.md). -# Volume Expansion for Qdrant +# Qdrant Volume Expansion -This guide shows how to increase PVC size of Qdrant data volumes. +This guide will give an overview of how KubeDB Ops-manager expands the volume of a `Qdrant` database. ## Before You Begin -- Ensure your StorageClass supports volume expansion. -- Use the example files from `docs/examples/qdrant/quickstart/distributed.yaml` and `docs/examples/qdrant/volume-expansion/ops-request.yaml`. +- You should be familiar with the following `KubeDB` concepts: + - [Qdrant](/docs/guides/qdrant/concepts/qdrant.md) + - [QdrantOpsRequest](/docs/guides/qdrant/concepts/opsrequest.md) -```bash -kubectl create ns demo -kubectl get storageclass -``` +## How Volume Expansion Works -## Deploy Qdrant +The Volume Expansion process consists of the following steps: -```bash -kubectl apply -f https://github.com/kubedb/docs/raw/{{< param "info.version" >}}/docs/examples/qdrant/quickstart/distributed.yaml -kubectl get qdrant -n demo qdrant-sample -w -``` +1. At first, a user creates a `Qdrant` Custom Resource (CR). -## Apply VolumeExpansion OpsRequest +2. `KubeDB-Provisioner` operator watches the `Qdrant` CR. -```bash -kubectl apply -f https://github.com/kubedb/docs/raw/{{< param "info.version" >}}/docs/examples/qdrant/volume-expansion/ops-request.yaml -kubectl get qdrantopsrequest -n demo qdrant-volume-expand -``` +3. When the operator finds a `Qdrant` CR, it creates a `StatefulSet` and related necessary stuff like pods, PVCs, secrets, services, etc. -## Verify +4. Each StatefulSet creates a Persistent Volume according to the volume claim template. This Persistent Volume will be expanded by the `KubeDB` Ops-manager operator. -```bash -kubectl describe qdrantopsrequest -n demo qdrant-volume-expand -``` +5. Then, in order to expand the volume of the `Qdrant` database, the user creates a `QdrantOpsRequest` CR with the desired new storage size. -## Cleaning up +6. `KubeDB` Ops-manager operator watches the `QdrantOpsRequest` CR. -```bash -kubectl delete qdrantopsrequest -n demo qdrant-volume-expand -kubectl delete qdrant -n demo qdrant-sample -kubectl delete ns demo -``` +7. When it finds a `QdrantOpsRequest` CR, it pauses the `Qdrant` object so that the `KubeDB-Provisioner` operator doesn't perform any operations on the `Qdrant` during the volume expansion process. + +8. Then the `KubeDB` Ops-manager operator expands the persistent volumes to the expected size defined in the `QdrantOpsRequest` CR. + +9. After the successful expansion of the volumes, the `KubeDB` Ops-manager updates the new volume size in the `Qdrant` object to reflect the updated state. + +10. After the successful Volume Expansion, the `KubeDB` Ops-manager resumes the `Qdrant` object so that the `KubeDB-Provisioner` resumes its usual operations. + +In the next doc, we are going to show a step-by-step guide on Volume Expansion of a Qdrant database using `QdrantOpsRequest` CRD. diff --git a/docs/guides/qdrant/volume-expansion/volume-expansion.md b/docs/guides/qdrant/volume-expansion/volume-expansion.md index 37f80d0d1..4fb37688c 100644 --- a/docs/guides/qdrant/volume-expansion/volume-expansion.md +++ b/docs/guides/qdrant/volume-expansion/volume-expansion.md @@ -14,42 +14,239 @@ section_menu_id: guides # Expand Qdrant Volume -This guide shows how to expand persistent volume size for Qdrant nodes using `QdrantOpsRequest`. +This guide will show you how to use `KubeDB` Ops-manager operator to expand the volume of a Qdrant database. ## Before You Begin -- Your `StorageClass` must support volume expansion. -- Install KubeDB Community and Enterprise operators. +- At first, you need to have a Kubernetes cluster, and the `kubectl` command-line tool must be configured to communicate with your cluster. + +- You must have a `StorageClass` that supports volume expansion. + +- Install `KubeDB` Provisioner and Ops-manager operator in your cluster following the steps [here](/docs/setup/README.md). + +- You should be familiar with the following `KubeDB` concepts: + - [Qdrant](/docs/guides/qdrant/concepts/qdrant.md) + - [QdrantOpsRequest](/docs/guides/qdrant/concepts/opsrequest.md) + - [Volume Expansion Overview](/docs/guides/qdrant/volume-expansion/overview.md) + +To keep everything isolated, we are going to use a separate namespace called `demo` throughout this tutorial. + +```bash +$ kubectl create ns demo +namespace/demo created +``` + +> **Note:** YAML files used in this tutorial are stored in [docs/guides/qdrant/volume-expansion/yamls](/docs/guides/qdrant/volume-expansion/yamls) directory of [kubedb/docs](https://github.com/kubedb/docs) repository. + +## Expand Volume of Qdrant Database + +Here, we are going to deploy a `Qdrant` cluster using a supported version by `KubeDB` operator. Then we are going to apply `QdrantOpsRequest` to expand its volume. + +### Prepare Qdrant Database + +At first verify that your cluster has a storage class that supports volume expansion. Let's check, ```bash $ kubectl get storageclass +NAME PROVISIONER RECLAIMPOLICY VOLUMEBINDINGMODE ALLOWVOLUMEEXPANSION AGE +standard (default) rancher.io/local-path Delete WaitForFirstConsumer false 5m +standard-expandable kubernetes.io/gce-pd Delete Immediate true 5m ``` -## Apply VolumeExpansion OpsRequest +We can see the `standard-expandable` storage class has `ALLOWVOLUMEEXPANSION` field as `true`. So, this storage class supports volume expansion. We can use it. + +Now, we are going to deploy a `Qdrant` cluster with version `1.17.0`. + +#### Deploy Qdrant + +In this section, we are going to deploy a Qdrant cluster with 1Gi volume. Then, in the next section we will expand its volume to 3Gi using `QdrantOpsRequest` CRD. Below is the YAML of the `Qdrant` CR that we are going to create, + +```yaml +apiVersion: kubedb.com/v1alpha2 +kind: Qdrant +metadata: + name: qdrant-sample + namespace: demo +spec: + version: "1.17.0" + replicas: 3 + storage: + storageClassName: "standard-expandable" + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 1Gi + deletionPolicy: WipeOut +``` + +Let's create the `Qdrant` CR we have shown above, + +```bash +$ kubectl create -f https://github.com/kubedb/docs/raw/{{< param "info.version" >}}/docs/guides/qdrant/volume-expansion/yamls/qdrant.yaml +qdrant.kubedb.com/qdrant-sample created +``` + +Now, wait until `qdrant-sample` has status `Ready`: + +```bash +$ kubectl get qdrant -n demo +NAME VERSION STATUS AGE +qdrant-sample 1.17.0 Ready 3m47s +``` + +Let's check volume size from the PetSet and from the persistent volumes: + +```bash +$ kubectl get petset -n demo qdrant-sample -o json | jq '.spec.volumeClaimTemplates[].spec.resources.requests.storage' +"1Gi" + +$ kubectl get pvc -n demo +NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE +qdrant-sample-qdrant-sample-0 Bound pvc-a1b2c3d4-e5f6-7890-abcd-ef1234567890 1Gi RWO standard-expandable 4m +qdrant-sample-qdrant-sample-1 Bound pvc-b2c3d4e5-f6a7-8901-bcde-f01234567891 1Gi RWO standard-expandable 3m +qdrant-sample-qdrant-sample-2 Bound pvc-c3d4e5f6-a7b8-9012-cdef-012345678902 1Gi RWO standard-expandable 2m +``` + +You can see the PetSet has 1Gi storage, and the capacity of the persistent volumes is also 1Gi. + +We are now ready to apply the `QdrantOpsRequest` CR to expand the volume of this database. + +### Volume Expansion + +Here, we are going to expand the volume of the Qdrant cluster. + +#### Create QdrantOpsRequest + +In order to expand the volume of the database, we have to create a `QdrantOpsRequest` CR with our desired volume size. Below is the YAML of the `QdrantOpsRequest` CR that we are going to create, ```yaml apiVersion: ops.kubedb.com/v1alpha1 kind: QdrantOpsRequest metadata: - name: qdrant-volume-expansion + name: qdops-vol-exp namespace: demo spec: - type: VolumeExpansion + apply: IfReady databaseRef: name: qdrant-sample + type: VolumeExpansion volumeExpansion: mode: Online - node: 5Gi + node: 3Gi +``` + +Here, + +- `spec.databaseRef.name` specifies that we are performing volume expansion operation on `qdrant-sample` Qdrant database. +- `spec.type` specifies that we are performing `VolumeExpansion` on our database. +- `spec.volumeExpansion.node` specifies the desired volume size. +- `spec.volumeExpansion.mode` specifies the desired volume expansion mode (`Online` or `Offline`). + +> Note: If the StorageClass doesn't support `Online` volume expansion, try offline volume expansion by using `spec.volumeExpansion.mode: "Offline"`. + +During `Online` VolumeExpansion KubeDB expands volume without pausing the database object; it directly updates the underlying PVC. For `Offline` volume expansion, the database is paused, the Pods are deleted, the PVC is updated, and then the database Pods are recreated with the updated PVC. + +Let's create the `QdrantOpsRequest` CR we have shown above, + +```bash +$ kubectl apply -f https://github.com/kubedb/docs/raw/{{< param "info.version" >}}/docs/guides/qdrant/volume-expansion/yamls/qdops-vol-exp.yaml +qdrantopsrequest.ops.kubedb.com/qdops-vol-exp created +``` + +#### Verify Qdrant volume expanded successfully + +If everything goes well, `KubeDB` Ops-manager operator will update the volume size of the `Qdrant` object and related `PetSet` and `Persistent Volumes`. + +Let's wait for `QdrantOpsRequest` to be `Successful`. Run the following command to watch `QdrantOpsRequest` CR, + +```bash +$ kubectl get qdrantopsrequest -n demo +NAME TYPE STATUS AGE +qdops-vol-exp VolumeExpansion Successful 10m +``` + +We can see from the above output that the `QdrantOpsRequest` has succeeded. If we describe the `QdrantOpsRequest` we will get an overview of the steps that were followed to expand the volume of the database. + +```bash +$ kubectl describe qdrantopsrequest qdops-vol-exp -n demo +Name: qdops-vol-exp +Namespace: demo +Labels: +Annotations: +API Version: ops.kubedb.com/v1alpha1 +Kind: QdrantOpsRequest +Spec: + Apply: IfReady + Database Ref: + Name: qdrant-sample + Type: VolumeExpansion + Volume Expansion: + Mode: Online + Node: 3Gi +Status: + Conditions: + Last Transition Time: 2026-05-01T10:04:19Z + Message: Qdrant ops request is expanding volume of database + Observed Generation: 1 + Reason: Running + Status: True + Type: Running + Last Transition Time: 2026-05-01T10:05:12Z + Message: Online Volume Expansion performed successfully in Qdrant pods for QdrantOpsRequest: demo/qdops-vol-exp + Observed Generation: 1 + Reason: VolumeExpansion + Status: True + Type: VolumeExpansion + Last Transition Time: 2026-05-01T10:06:08Z + Message: PetSet is recreated + Observed Generation: 1 + Reason: ReadyPetSets + Status: True + Type: ReadyPetSets + Last Transition Time: 2026-05-01T10:06:52Z + Message: Successfully Expanded Volume. + Observed Generation: 1 + Reason: Successful + Status: True + Type: Successful + Observed Generation: 1 + Phase: Successful +Events: + Type Reason Age From Message + ---- ------ ---- ---- ------- + Normal PauseDatabase 12m KubeDB Ops-manager Operator Pausing Qdrant demo/qdrant-sample + Normal PauseDatabase 12m KubeDB Ops-manager Operator Successfully paused Qdrant demo/qdrant-sample + Normal VolumeExpansion 11m KubeDB Ops-manager Operator Online Volume Expansion performed successfully in Qdrant pods for QdrantOpsRequest: demo/qdops-vol-exp + Normal ResumeDatabase 11m KubeDB Ops-manager Operator Resuming Qdrant demo/qdrant-sample + Normal ResumeDatabase 11m KubeDB Ops-manager Operator Successfully resumed Qdrant demo/qdrant-sample + Normal ReadyPetSets 10m KubeDB Ops-manager Operator PetSet is recreated + Normal Successful 10m KubeDB Ops-manager Operator Successfully Expanded Volume ``` +Now, we are going to verify from the `PetSet` and `Persistent Volumes` whether the volume of the Qdrant database has expanded to meet the desired state: + ```bash -$ kubectl apply -f qdrant-volume-expansion.yaml -qdrantopsrequest.ops.kubedb.com/qdrant-volume-expansion created +$ kubectl get petset -n demo qdrant-sample -o json | jq '.spec.volumeClaimTemplates[].spec.resources.requests.storage' +"3Gi" + +$ kubectl get pvc -n demo +NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE +qdrant-sample-qdrant-sample-0 Bound pvc-a1b2c3d4-e5f6-7890-abcd-ef1234567890 3Gi RWO standard-expandable 14m +qdrant-sample-qdrant-sample-1 Bound pvc-b2c3d4e5-f6a7-8901-bcde-f01234567891 3Gi RWO standard-expandable 13m +qdrant-sample-qdrant-sample-2 Bound pvc-c3d4e5f6-a7b8-9012-cdef-012345678902 3Gi RWO standard-expandable 12m ``` -## Verify +The above output verifies that we have successfully expanded the volume of the Qdrant database. + +## Cleaning Up + +To clean up the Kubernetes resources created by this tutorial, run: ```bash -$ kubectl get qdrantopsrequest -n demo qdrant-volume-expansion -$ kubectl describe qdrantopsrequest -n demo qdrant-volume-expansion +$ kubectl delete qdrant -n demo qdrant-sample +qdrant.kubedb.com "qdrant-sample" deleted + +$ kubectl delete qdrantopsrequest -n demo qdops-vol-exp +qdrantopsrequest.ops.kubedb.com "qdops-vol-exp" deleted ``` \ No newline at end of file From f453856e8921a54ed032cd74b2bbfcad9853bbab Mon Sep 17 00:00:00 2001 From: Abdullah Al Masood <98589987+al-masood@users.noreply.github.com> Date: Mon, 4 May 2026 11:00:51 +0600 Subject: [PATCH 03/10] re-ordering Signed-off-by: Abdullah Al Masood <98589987+al-masood@users.noreply.github.com> --- docs/guides/qdrant/README.md | 84 ++----- docs/guides/qdrant/autoscaler/_index.md | 2 +- docs/guides/qdrant/backup/_index.md | 4 +- docs/guides/qdrant/concepts/_index.md | 2 +- docs/guides/qdrant/configuration/_index.md | 2 +- docs/guides/qdrant/custom-rbac/_index.md | 10 - .../qdrant/custom-rbac/using-custom-rbac.md | 215 ----------------- .../guides/qdrant/images/qdrant-lifecycle.png | Bin 0 -> 96030 bytes docs/guides/qdrant/monitoring/_index.md | 2 +- docs/guides/qdrant/ops-request/_index.md | 10 - docs/guides/qdrant/ops-request/overview.md | 51 ----- docs/guides/qdrant/private-registry/_index.md | 10 - .../using-private-registry.md | 145 ------------ docs/guides/qdrant/quickstart/_index.md | 2 +- docs/guides/qdrant/quickstart/rbac.md | 216 ------------------ docs/guides/qdrant/reconfigure-tls/_index.md | 2 +- docs/guides/qdrant/reconfigure/_index.md | 2 +- docs/guides/qdrant/restart/_index.md | 2 +- docs/guides/qdrant/rotate-auth/_index.md | 2 +- docs/guides/qdrant/scaling/_index.md | 2 +- docs/guides/qdrant/tls/_index.md | 2 +- docs/guides/qdrant/update-version/_index.md | 2 +- docs/guides/qdrant/volume-expansion/_index.md | 2 +- 23 files changed, 31 insertions(+), 740 deletions(-) delete mode 100644 docs/guides/qdrant/custom-rbac/_index.md delete mode 100644 docs/guides/qdrant/custom-rbac/using-custom-rbac.md create mode 100644 docs/guides/qdrant/images/qdrant-lifecycle.png delete mode 100644 docs/guides/qdrant/ops-request/_index.md delete mode 100644 docs/guides/qdrant/ops-request/overview.md delete mode 100644 docs/guides/qdrant/private-registry/_index.md delete mode 100644 docs/guides/qdrant/private-registry/using-private-registry.md delete mode 100644 docs/guides/qdrant/quickstart/rbac.md diff --git a/docs/guides/qdrant/README.md b/docs/guides/qdrant/README.md index dc5d8a50f..1313aabe4 100644 --- a/docs/guides/qdrant/README.md +++ b/docs/guides/qdrant/README.md @@ -21,79 +21,27 @@ KubeDB supports Qdrant vector databases through the `Qdrant` CRD. ## Supported Qdrant Features -| Features | Availability | -|-------------------------------------|:------------:| -| Standalone provisioning | ✓ | -| Distributed provisioning | ✓ | -| TLS | ✓ | -| Backup (logical and volume snapshot)| ✓ | -| Monitoring | ✓ | -| Ops Requests | ✓ | -| Autoscaler | No | +| Features | Availability | +|--------------------------|:------------:| +| Standalone provisioning | ✓ | +| Distributed provisioning | ✓ | +| TLS | ✓ | +| Logical Backup | ✓ | +| Volume Snapshot | ✓ | +| Monitoring | ✓ | +| Ops Requests | ✓ | +| Autoscaler | ✓ | -## Supported Ops Requests -- HorizontalScaling -- Reconfigure -- ReconfigureTLS -- Restart -- RotateAuth -- VerticalScaling -- VolumeExpansion -- UpdateVersion +## Life Cycle of a Qdrant Object -## Example Qdrant Manifest +

+  lifecycle +

-```yaml -apiVersion: kubedb.com/v1alpha2 -kind: Qdrant -metadata: - name: qdrant-sample -spec: - version: 1.17.0 - mode: Distributed - replicas: 3 - storage: - accessModes: - - ReadWriteOnce - resources: - requests: - storage: 2Gi - deletionPolicy: WipeOut -``` ## User Guide - [Quickstart Qdrant](/docs/guides/qdrant/quickstart/quickstart.md) with KubeDB operator. -- [Qdrant CRD Concept](/docs/guides/qdrant/concepts/qdrant.md). -- [QdrantVersion CRD Concept](/docs/guides/qdrant/concepts/catalog.md). -- [QdrantOpsRequest CRD Concept](/docs/guides/qdrant/concepts/opsrequest.md). -- [QdrantAutoscaler CRD Concept](/docs/guides/qdrant/concepts/autoscaler.md). -- [Monitoring](/docs/guides/qdrant/monitoring/overview.md) for metrics collection guidance. -- [TLS](/docs/guides/qdrant/tls/overview.md) for client and p2p security guidance. -- [Backup](/docs/guides/qdrant/backup/overview.md) for logical and snapshot backup approaches. -- [Ops Request](/docs/guides/qdrant/ops-request/overview.md) for supported operational changes. -- [Autoscaler](/docs/guides/qdrant/autoscaler/overview.md) for current documentation status. -- [Private Registry](/docs/guides/qdrant/private-registry/using-private-registry.md) -- [Custom RBAC](/docs/guides/qdrant/custom-rbac/using-custom-rbac.md) -- [Custom Configuration](/docs/guides/qdrant/configuration/using-config-file.md) -- [Builtin Prometheus Monitoring](/docs/guides/qdrant/monitoring/using-builtin-prometheus.md) -- [Prometheus Operator Monitoring](/docs/guides/qdrant/monitoring/using-prometheus-operator.md) -- [Configure TLS](/docs/guides/qdrant/tls/configure/) -- [Reconfigure Details](/docs/guides/qdrant/reconfigure/reconfigure.md) -- [Reconfigure TLS Details](/docs/guides/qdrant/reconfigure-tls/reconfigure-tls.md) -- [Rotate Auth Details](/docs/guides/qdrant/rotate-auth/rotateauth.md) -- [Horizontal Scaling Details](/docs/guides/qdrant/scaling/horizontal-scaling/scale-horizontally/) -- [Vertical Scaling Details](/docs/guides/qdrant/scaling/vertical-scaling/scale-vertically/) -- [Update Version Details](/docs/guides/qdrant/update-version/versionupgrading/) -- [Volume Expansion Details](/docs/guides/qdrant/volume-expansion/volume-expansion.md) -- [Reconfigure](/docs/guides/qdrant/reconfigure/overview.md) -- [Reconfigure TLS](/docs/guides/qdrant/reconfigure-tls/overview.md) -- [Restart](/docs/guides/qdrant/restart/restart.md) -- [Rotate Auth](/docs/guides/qdrant/rotate-auth/overview.md) -- [Update Version](/docs/guides/qdrant/update-version/overview.md) -- [Volume Expansion](/docs/guides/qdrant/volume-expansion/overview.md) -- [Horizontal Scaling](/docs/guides/qdrant/scaling/horizontal-scaling/overview.md) -- [Vertical Scaling](/docs/guides/qdrant/scaling/vertical-scaling/overview.md) -- [Compute Autoscaler](/docs/guides/qdrant/autoscaler/compute/overview.md) -- [Storage Autoscaler](/docs/guides/qdrant/autoscaler/storage/overview.md) \ No newline at end of file +- Detail concepts of [Qdrant Object](/docs/guides/qdrant/concepts/qdrant.md). +- Want to hack on KubeDB? Check our [contribution guidelines](/docs/CONTRIBUTING.md). diff --git a/docs/guides/qdrant/autoscaler/_index.md b/docs/guides/qdrant/autoscaler/_index.md index 2bc9ca6aa..2abd242bd 100644 --- a/docs/guides/qdrant/autoscaler/_index.md +++ b/docs/guides/qdrant/autoscaler/_index.md @@ -5,6 +5,6 @@ menu: identifier: qdrant-autoscaler name: Autoscaler parent: qdrant-guides - weight: 40 + weight: 130 menu_name: docs_{{ .version }} --- diff --git a/docs/guides/qdrant/backup/_index.md b/docs/guides/qdrant/backup/_index.md index 05c7cb9cb..c4dc974b7 100644 --- a/docs/guides/qdrant/backup/_index.md +++ b/docs/guides/qdrant/backup/_index.md @@ -3,8 +3,8 @@ title: Qdrant Backup menu: docs_{{ .version }}: identifier: qdrant-backup - name: Backup + name: Backup & Restore parent: qdrant-guides - weight: 30 + weight: 50 menu_name: docs_{{ .version }} --- diff --git a/docs/guides/qdrant/concepts/_index.md b/docs/guides/qdrant/concepts/_index.md index b8aa8fa9e..4f82fb0c8 100644 --- a/docs/guides/qdrant/concepts/_index.md +++ b/docs/guides/qdrant/concepts/_index.md @@ -5,6 +5,6 @@ menu: identifier: qdrant-concepts name: Concepts parent: qdrant-guides - weight: 15 + weight: 20 menu_name: docs_{{ .version }} --- \ No newline at end of file diff --git a/docs/guides/qdrant/configuration/_index.md b/docs/guides/qdrant/configuration/_index.md index c18554252..8d80a49d6 100644 --- a/docs/guides/qdrant/configuration/_index.md +++ b/docs/guides/qdrant/configuration/_index.md @@ -5,6 +5,6 @@ menu: identifier: qdrant-configuration name: Custom Configuration parent: qdrant-guides - weight: 130 + weight: 30 menu_name: docs_{{ .version }} --- \ No newline at end of file diff --git a/docs/guides/qdrant/custom-rbac/_index.md b/docs/guides/qdrant/custom-rbac/_index.md deleted file mode 100644 index 6807300eb..000000000 --- a/docs/guides/qdrant/custom-rbac/_index.md +++ /dev/null @@ -1,10 +0,0 @@ ---- -title: Run Qdrant with Custom RBAC resources -menu: - docs_{{ .version }}: - identifier: qdrant-custom-rbac - name: Custom RBAC - parent: qdrant-guides - weight: 90 -menu_name: docs_{{ .version }} ---- \ No newline at end of file diff --git a/docs/guides/qdrant/custom-rbac/using-custom-rbac.md b/docs/guides/qdrant/custom-rbac/using-custom-rbac.md deleted file mode 100644 index 4bba42d43..000000000 --- a/docs/guides/qdrant/custom-rbac/using-custom-rbac.md +++ /dev/null @@ -1,215 +0,0 @@ ---- -title: Run Qdrant with Custom RBAC resources -menu: - docs_{{ .version }}: - identifier: qdrant-custom-rbac-quickstart - name: Custom RBAC - parent: qdrant-custom-rbac - weight: 10 -menu_name: docs_{{ .version }} -section_menu_id: guides ---- - -> New to KubeDB? Please start [here](/docs/README.md). - -# Using Custom RBAC Resources - -KubeDB supports finer user control over role-based access permissions provided to a Qdrant instance. This tutorial will show you how to use KubeDB to run a Qdrant instance with custom RBAC resources. - -## Before You Begin - -- At first, you need to have a Kubernetes cluster, and the `kubectl` command-line tool must be configured to communicate with your cluster. If you do not already have a cluster, you can create one by using [kind](https://kind.sigs.k8s.io/docs/user/quick-start/). - -- Now, install KubeDB cli on your workstation and KubeDB operator in your cluster following the steps [here](/docs/setup/README.md). - -- To keep things isolated, this tutorial uses a separate namespace called `demo` throughout this tutorial. - -```bash -$ kubectl create ns demo -namespace/demo created -``` - -> **Note:** YAML files used in this tutorial are stored in [docs/examples/qdrant/custom-rbac](https://github.com/kubedb/docs/tree/{{< param "info.version" >}}/docs/examples/qdrant/custom-rbac) folder in GitHub repository [kubedb/docs](https://github.com/kubedb/docs). - -## Overview - -KubeDB allows users to provide custom RBAC resources, namely `ServiceAccount`, `Role`, and `RoleBinding` for Qdrant. This is provided via the `spec.podTemplate.spec.serviceAccountName` field in the Qdrant CRD. If this field is left empty, the KubeDB operator will create a ServiceAccount matching the Qdrant name. Role and RoleBinding that provide necessary access permissions will also be generated automatically for this ServiceAccount. - -If a ServiceAccount name is given but there is no existing ServiceAccount by that name, the KubeDB operator will create one, and Role and RoleBinding will also be generated automatically. - -If a ServiceAccount name is given and there is an existing ServiceAccount by that name, the KubeDB operator will use that existing ServiceAccount. Since this ServiceAccount is not managed by KubeDB, users are responsible for providing necessary access permissions manually. - -This guide will show you how to create custom `ServiceAccount`, `Role`, and `RoleBinding` for a Qdrant instance named `qdrant-custom-rbac` to provide the bare minimum access permissions. - -## Custom RBAC for Qdrant - -At first, let's create a `ServiceAccount` in the `demo` namespace: - -```bash -$ kubectl create serviceaccount -n demo my-custom-serviceaccount -serviceaccount/my-custom-serviceaccount created -``` - -Verify that the ServiceAccount was created: - -```yaml -$ kubectl get serviceaccount -n demo my-custom-serviceaccount -o yaml -apiVersion: v1 -kind: ServiceAccount -metadata: - name: my-custom-serviceaccount - namespace: demo -``` - -Now, create a Role that has the necessary access permissions for the Qdrant database named `qdrant-custom-rbac`: - -```bash -$ kubectl apply -f https://github.com/kubedb/docs/raw/{{< param "info.version" >}}/docs/examples/qdrant/custom-rbac/qdrant-custom-role.yaml -role.rbac.authorization.k8s.io/my-custom-role created -``` - -Below is the YAML for the Role we just created: - -```yaml -apiVersion: rbac.authorization.k8s.io/v1 -kind: Role -metadata: - name: my-custom-role - namespace: demo -rules: -- apiGroups: - - apps - resourceNames: - - qdrant-custom-rbac - resources: - - statefulsets - verbs: - - get -- apiGroups: - - kubedb.com - resourceNames: - - qdrant-custom-rbac - resources: - - qdrants - verbs: - - get -- apiGroups: - - "" - resources: - - pods - verbs: - - list - - patch - - delete -- apiGroups: - - "" - resources: - - pods/exec - verbs: - - create -- apiGroups: - - "" - resources: - - secrets - verbs: - - get - - list -- apiGroups: - - "" - resources: - - configmaps - verbs: - - create - - get - - update -``` - -Note that `resourceNames` like `qdrant-custom-rbac` are unique to this particular Qdrant instance. Another database instance `qdrant-custom-rbac-2` would require these `resourceNames` to be updated accordingly. - -Now, create a `RoleBinding` to bind this `Role` with the already created ServiceAccount: - -```bash -$ kubectl create rolebinding my-custom-rolebinding \ - --role=my-custom-role \ - --serviceaccount=demo:my-custom-serviceaccount \ - --namespace=demo -rolebinding.rbac.authorization.k8s.io/my-custom-rolebinding created -``` - -Verify the RoleBinding was created: - -```yaml -$ kubectl get rolebinding -n demo my-custom-rolebinding -o yaml -apiVersion: rbac.authorization.k8s.io/v1 -kind: RoleBinding -metadata: - name: my-custom-rolebinding - namespace: demo -roleRef: - apiGroup: rbac.authorization.k8s.io - kind: Role - name: my-custom-role -subjects: -- kind: ServiceAccount - name: my-custom-serviceaccount - namespace: demo -``` - -Now, create a `Qdrant` CR specifying `spec.podTemplate.spec.serviceAccountName` field to `my-custom-serviceaccount`: - -```bash -$ kubectl apply -f https://github.com/kubedb/docs/raw/{{< param "info.version" >}}/docs/examples/qdrant/custom-rbac/qdrant-custom-db.yaml -qdrant.kubedb.com/qdrant-custom-rbac created -``` - -Below is the YAML for the `Qdrant` CR we just created: - -```yaml -apiVersion: kubedb.com/v1alpha2 -kind: Qdrant -metadata: - name: qdrant-custom-rbac - namespace: demo -spec: - version: "1.17.0" - replicas: 3 - podTemplate: - spec: - serviceAccountName: my-custom-serviceaccount - storage: - storageClassName: "standard" - accessModes: - - ReadWriteOnce - resources: - requests: - storage: 1Gi - deletionPolicy: WipeOut -``` - -Now, wait a few minutes. If everything goes well, we will see that the Qdrant pods are running with the custom ServiceAccount: - -```bash -$ kubectl get qdrant -n demo qdrant-custom-rbac -NAME VERSION STATUS AGE -qdrant-custom-rbac 1.17.0 Ready 2m - -$ kubectl get pod -n demo -l app.kubernetes.io/instance=qdrant-custom-rbac -o=custom-columns=NAME:.metadata.name,SERVICEACCOUNT:.spec.serviceAccountName -NAME SERVICEACCOUNT -qdrant-custom-rbac-0 my-custom-serviceaccount -qdrant-custom-rbac-1 my-custom-serviceaccount -qdrant-custom-rbac-2 my-custom-serviceaccount -``` - -The output confirms that all Qdrant pods are running with our custom `my-custom-serviceaccount` ServiceAccount. - -## Cleaning up - -To clean up the Kubernetes resources created by this tutorial, run: - -```bash -kubectl delete qdrant -n demo qdrant-custom-rbac -kubectl delete rolebinding -n demo my-custom-rolebinding -kubectl delete role -n demo my-custom-role -kubectl delete serviceaccount -n demo my-custom-serviceaccount -kubectl delete ns demo -``` \ No newline at end of file diff --git a/docs/guides/qdrant/images/qdrant-lifecycle.png b/docs/guides/qdrant/images/qdrant-lifecycle.png new file mode 100644 index 0000000000000000000000000000000000000000..d86e5323e474b51e23caa495645806af78d0203d GIT binary patch literal 96030 zcmbrm1yEGs*FU}pf+(O8f+!6lAt2o?EZrcDk_#*#-64q5-6hg3Ew!MibT2I3-LQ22 z@2=nXH@}&8e($_9|D6Hu-uv9=p68tNocNq`2~ky+#lKH}9|Qv7%YmiUL7=PU^Cfpz{9b8^9MVO9>?j5U4Wh!PUFF zz;^=YH*cLa>@A$#-Z`3sWFVH%cbS!mrXUR06gg=LO?Qm#865v~myFwky_%htT-#VB zuoW)-m%AbASQ11`n=?${%4-s0RTUBOpPu*c%S;I#fyKQp6uw|}?awyxP&u(!W=WNo5PIP3hzS4@S2OcAxZ!Ov!#5Ids^ zgz#*wO;T^Bh`zUImPb06-fX7PWmVvEeCZ(h88B|1#vcm=5*H*Fv`)4Plv#7ify3cP z_n)|sU%vITv$l@Cy7ERom!JoR5@Vo#dw#%)I&e3boSHK4j%N~)So|yU_OAeqkG_Y z?`TjIwPC#h;etRMKN|ui@F{DHE5CjFwsyS67rY#7T-!-E;39ub-x~kKlOg61cX-Pz zG$SV0c9tR`Zf&*an5sD14VL|V?RacVyV~}dbgD<=Wjw%%wlf*@oDvOz9bo$RnNB!S zeqI9fa|V;FEJpWJEXLlPXG(qS&=h41!ILGbg18w(rjbOm8>_z96KN4K(qv5JvHgkQe5^um`5Zy(xLo5;%>UC= zSoB9!xM*3v^XkM8p%=1>2R%GgFB2YSYO{LWlz&$X1XIBOVrMogVZ{K`E)k)m(T24^ zK*@78(hb-UP5Vr%y4$BXw>ZKLhkSY^O{Xn*#hkau%z#?w5nZ0 ztCH((SZb(j>j2$>w5u4c^`oM`vij{XIk^B?Q77LNZ%4I|_@pz>y2|Wq33&wtFG?cx zoDcua887tP#@04|a#F8rV1O@5s9mmMYXEvUr@TKtPMI{Jan)Z#X_-0g%YBpzqmHX; zp|Qg*KZkqCFNY%K^XjJUjS$g}h;l=9EiIQ$QGiGjTUviWVq})n*)uoSm*$)BB9Wh_ zHkOih#$(L&uWaH}rOn37vWPAq^!!!a!SFUG9aF_$%48Rn)nO56B5pFmIjhFA`uq1m;TK;}^m+Z8kpq97J05x7c%crL zFmyI~`fPAV@KN0Dg<;s$w#tsW(RKopmhVWfC$}#MPF*#GD}T!SXT|5igOw6et29vxpz{8p@}P2H?HH^eE9uJpdwGWONhcp$Sv-+NE2Z@AM72(6SXeJiHb)H?>kgm< z{zG9PLV&+`RoBA7?eB^JUH0(Z8T;bB2D9*(-ylfQUP=ljtl%mOmlFglainv<+uV3? zxMD-9)jhRFa@1Ttnn^A>N&N(1XU;!0CCk-{iUH>ec`J6`^XYaWOOUEZpX_6Sf*eyynFa)4QExIE~B1mBEV;?!Uc)^%fv@cDEI~CxtfY=TEx< zvjc2sMPNM~fw`W>hl!*-p(Ik*GeCXvbK|2r@7zcYkR!0d02bf*y0j(d(3zBsR=A{MSYi`KzEHBNUu+^5 zwf3@%DiVcTcei_SrP1=3i=NLM$1T6=SXNNL7Qsp&^;$WPc#L%N^@Ra5nH>GwZ&Ee4 zvJ~!ljg6DSM%Q|Z%%M2D;vc-E3}9}$S_4Z%+(uzt)FSUw18?Uww5kf2c;|&bc^G-@ z))qR;5ayF>roF=i=5qe8xvanE<^7r7bNSe)<0g~Nh*M71i}YGvhXm}JcNXSOY3Z!Q z$&tV?F7x35Ry7kR`7>(BA7y1qXj%M6rd_+vyLn}5ArB2$t-{9!4^+Ft#lEiM2#fT6 zfAHV|%0d5^8ZK=p6W!iegF_#1-SQ_zJg#slO@k%Ib^bbZnMh&F|Mr`1pqY?YY)Zld zHU9<2eO{ETvlF7oMaM=*Q#wkqA@MJFyx%i&1 zPvkkmxm)Se8JlNgnCjs}tvapbujwq35)xAKH+)-L8NUMbw5^NZMOkmM?4NpGUS2w4 z(G<8I9$wS9Smx5>`vYyQh0xyVrKDxED-mpXEhA zot%B-bZ4mCms($6AD0S{u?!6}+H^&Ob4v!=`G*q ziDONUTMG)W;7ed*-Ul0M!LF`icyVQ85B;)=c#+}KG`ZShupa`nT-KP2NPJ;(et;7) zlq4vxVEP50TBr31Z}vQRu-0~tDz4(@o9?R6f~oKaE3t^zj30#SL{)sNnw2uA9>qMK z_3{3d?l_^KYh~J$-|iQmjz|1eiv)5MXqB73#P{0LFod2*u^{x zwPfTyDv89)_wJiBDy71?B+*1X{AclKpA#qm3i*4H)~5b9y%>k%HAVRhc}(bHfS6z+ zvHS99hT7Aj=YxC5&fpm}h+Wk2Q4C-J{D(fFZCd#H_BQva5P`JW+RyPXJU_J$+;te1 z{nJA(N@9C?%iL{oUzlA-CVSYX6{6Jih}s`z!s(pjEsvw{)k6L{J0!>0*a;W(vqQ53 z5Tv2>=3{29;maoQH+PLZR!6@BZ2B+VrrX@y?7i+zlHx3SZ-!%VzOI{Bbt-Vo&(Bx@XrK9gXwc?YT#E_zw*BmuS6MyyrX5Rn*ZW& zu!$A+%uRMAIQn9mfbpyM<&~uF)v6@d0Jsb3r9`yd4_3bu0P6dW(uSx1*!8ucVFIse zc#tp&ziEkZKf(Z1j11doYEI%pbB+# zbs@VtErf}wRWY{&x;P03&QT86k>__;^JxlKzt+js#k~=nv-6U129etUPE8v&&6EVI zmbSLG{zue59Tj(RscrB(sr?>LOc5MZkv4F=F|_I=$(}mGa7&pOc)g ztpdAs>EH)?j;{|<{##8sjkv_p2vR)i&)ue zkS#@d?nLg96@jdmVTV4PHdQK-X(^2MSxM}S1AR$dB(ThtA_KmG@>i$bBkZ4-)9ZFQtp~mU)TL3ZtKOQmfDw{--!~t<8jQ zccO@~gw=H&tx|tLjTOhBQQZ`NwnVLQBANV*19vkKTLTGuGfpBAv)Tbt36@ z*GyM8#o5_8`96?Ofu0-v^JZ`PpYzh0Y^{$B*oTAVvJpoQQlN%mim4LjzV)0hb#80q zPWU>^$LL$(h%@>)p4ByvUZp_!i0c}$zXLagYOtdbne)TX%*)klNyprM{cqTkN%y_^^C4z5o$yev>X7!dPXT znGF7-ndG%nOQ8m&U}cAUdsKQO>j6MWw9ymgx>9VOh@pOvx!`GqN4V4*P-F znsnA@VX6IC_3fKvzsRY9z%g9I%gDISHpOG78QISjmBePq%w^FVT~gjC4-oU06KzP; z6a$%SFKP=l)&*SfF}bK1c7e8obv?4x2?P|d*WPJFvn<=y_$SxYr&u_dRG z!cSP!eXyEl&%4#MUDcCYUfv%b3r*9vLE7X=EH!%|J?+Rvi{U%YpV7%32HKY8yhVWf zDN|l|F}it|>#^Ow%VABtIHO*5@`rC&C2G9VP###FEU6gv#hjJ8DzEi9EZeLGAD zV_sI0T?wJAR((kyATA#KeziAcb#}%w$2_aLuOvZR*!t5Fptu%yWGSL87JhvaQL0LS z`K~1BP*w?-1p|yH8~DepN+9{yxT|Yu_zEzzzwzb|(?}>dogBp%b@6BhgtNs%A2h40 zM^EMVEEIN@c1>L0+dTF(GuALrPDx^%`;j{=jF_Ib#m;`pjQ^lkQY}!L!sIUju_;vv zazL!r(i5M-rR}S}UBe$?*!;%_icYrpbSnG{SjcG&6%li02Ivn+(wZ+Guze~2#d+^5 z-zwgJ@F1_UQmweSc-c?P@YR6*m+qf+LGdJ>Rh+C!+==n0t!3lWGNIZI)^QsP!wf-m zxLn`+_F4regb|aI%pM*dtK;X_QkOqUw8F>*Cx^;hRG;FLL636?2%HZ7kfvqT;vO&l z3F{lsTPQ?sg2&`bp7EH#Aud5SWud}5H6y*;uDLdD2`JO*F8v>u;h!&|Cm|8=d+Ec0 zfZUYnTnZ~Hc9XyF9Y{WyR{_EN{H`q#_Dhxm&ywSEdBm5hUdF85a3sStA>OQND|w@m z-1^MQ5ZDASgyE0W17-2ad0xV!heSHVMJ+M^+LSlsuQhn>wbU0h9`pPP0Ps>O>6YvdQ_JNpL*vp!p0r<2dCbQ=Zg1p& zTv|SLaZYn)=iLrXSzWlZ9Cj)teE8mn-_u8IqJOdB_Ki0sZj9(R3Fc`aLbn4!KoBzrn)OK%oE+; zxbwJS`!i0iNPPSzX9bbu2~P`{KQSK6&9t?fY;oalYh%Mj@{PQAwHUP$kd!R?X@AEb zsoQ&a_-@<_={+B3OHp6p84d6m(jn|;Y=vIFUqps2LqHzMCDbm*mG0`2YB4FbqLzNb zKEYAld$!0Pedoax2xsl2C9{B#kdW6!Cm{T`bt&}J*m(r<)etwu$lD{z6fu?oZgWZ& zTjZ9aRrR)??Y*8QKBYvB$USmrnKUC7s4rj%{f9zSP%x)yG)26OtmRmgv(x64;{Xp& zm4y?1js!#|OtBu0Cs_T8dG5w4k4V%NU(jj34TDJ7Q%1ou5x5cj4;=^}263{pS2W!p zWZCocs`Nhb$*X#&13!x3H?pce6p45(fc)*xfk)S1fnnO6m0w_kl<4Ku7e1F?I%UC)u+? zv`1*?_O8+L&sH55KAlG}3M`}(4gUFibK0wS{lnDHfU*@5ic7_#I=ZiR*+!4rDP+(7 zVKDsoG(6D?^n@?cSvrxO8n-;UMpI9nB%3OygarUF$ItFwPgX0JT)yV!J%piBg^SqJ z19CtgxBq1UINM*jf$;E~#7rp@#`z4w+Pp9E%Tp20(jf^;qgl$BJtJ)fMyfQ%MiqQ~ zd^|zGgL7d2GhIZ0Z>wG|kEs+n%;Y2O^1HH0X|AKQzSB}ES;Ff^`9)yL_+^%tamu=^ zHicEek00Zjo`h)F;}xDHYMRJ#3COR7@0!}$scW;y(UvM-Sw-va=2)_otSfH4?Jx9y zwl)9}SN}~+)FvwBTnDUX+xR~N)eorNr93MWQw*S^9Y%Hf^7n-a_j?LgT?jiOnrN#Q zEJyP4r?<8h3}|D%6nW>)$pR`;8S-Krkn~bG#Z&!D1-Qk8M?kKyM-TnPcNS}zSjsX$ zA4z$GS1QvAlK0iNXr!_q1@;5u!#|*dpQY&FXmZiI+S(A`71@TYwVVo=8KBmFJJtKNr*SU zatRUqUTJv{wyd!Re4*ruCiwTnhyNnH$cYSDWT26X4K4dLZ(mS^;9{+|=szBv{fqniH>lYF-|%9Z1;CcfhUsok%32NwH=`BH zP82=_SpJsn82k3O-$DI^GQ3BwW3`KJE|c}sIc-Q#vM&|X0f5{6E2Gfa<(=;VM(^Hb z6Wwfdh*b#J)@j@vc^BADQIS^gL1e!62We|qg9|a+iHV6jO(5eb4w8dUd5i?0z5%Sh zPBCnw4d=hlZM(m6p;(WHf$}m~**`P2C7zIw;5Qk+*)di(FY%Kx%sjcYN;4SQqXPz< zNi+dCn?J5}AgsS%d7{;?6_O}o-c9_J75{#{-3+t2xw+%-$FiLcR(?^aSXsbn6%bjy z%B3I5hMY;Da0n2#c~>lhM!sSK-(JB5w#_HC!r3P z(Ildd{L|4QZ2P)iI9d#Y8KF)EJVGfj>eGqNMA3);(>&ODC?F^)Dd_<)l;aU9=mg=( zJ2_Q1R1_9I0=}3~qqPe)kz+85-KdXv|Nr|r{t^=~pQeT^0jGS_P)szDsH12u^h{tI zVA)l+6IBv@)fRo^fPDc?qgUtf1v*vNwUT7aMr!1qUHZ0?Hpj!R`9MbZheP(XLpwfd zVQ@CTjjxuc@jEcPGsA2C>-@p#w#T>5zT0l;0*cl2b@W5)<%b{!*+yqcPt&TntR zF7Rk-)z#ZO4i6nYmSbd>fuI{GHG$gDh?sbmUBk+ghO@;mFG(;o6)O9)yrP0{A?Jhy z(%u0CMq0o~5a0i)@t8Cs`W@bU4biL0|vxI~jb6CFMR>=tMP%^PYuJ&Y7V z>Im%Eo+Ng@Q{cR~AoovT=ZVYgHtoTTv?7C|fRmkA=%AC1)&9miX4jYawV*VjJi*MT z1-M3ic?GQ5|1s%h>n%|Vx17V-Ia{j%a3NXq|6YBbZB!xQs zZ&VayC z3;BseDOr`p&yROk>j2RcNwOKk3@rN7nI;Aa@w!v{(#*GDKI&@fw2Zx(ua4thm)%nQNE}u*3dbx|!fY%#-sy^5LA8B#8 zqcufW)5wS|O85}0;*e1G=x|KzN&@Dy*YYCBx#_5vYlVf>FkbD^#PlrN)XAP0{r6|M zU#<#$K|Id>+lR z!^biB{_8XePC%WcrHE{SrLtB7h@PZ1*Y*N$$IXezY3HcebrIkl{YM55n(JYs&90|Q zQ9VuRU&{m-lAlAlkKj$#k03Y2DT@<_VQVm^AUWMoKchyy$ra$ z;i`u-2xI$1p0{Z2nRtV;B;o{SC4}mjHP3ci5Le#UpGE17KiDPWPNeUv?Xh3}e7KBI zp0|L!^*A++>Pmg3Z}t_sDXX?gl#@?$tDyx#5)fUr7*>}%zZ}2sPcqAt^}_d&%xjg(6+LispR>|x&thU1@)?4rKJvm$4ohW~ zFRAjFTF>fc*S0l2Rf`Jkjf&k=3J?^*F1u7x=ZA)uzP(#v&oJFj*uCz;S^zAm_zBuo zFN2L~mem5^1_!%r5<;yr2%91E`F$}w-$%Zx28!cvao2e3xN%HWr|&)*QEHdP=X1Hh z$vEdZs?Ppeei|I_k;spc7wp+Mn+Z~vy>eYe_Ba8$|bczw@NqH&NJf#$J4dV79r2SJU)-e}kv+SFPG{uZ|v7*=o1D4AkioUG(UL?vFJYZsk zk5H>$nl&FbBh4(R0)MYl3&@x7S`xhXr{Kd^=V8?<=*!AF@Fq2I2*9OEfazQSuE^v_ zuilQrm>9zp3xl2`;IZ(XwgojcHJVDif?5nSbGf;gJbY^X^sj2|Qu=vkO9MXr%|>&E z0{nk)iS&n$NQ$!Ovkb18t+^~$${U;ExHOgm+1c9K`A!{{VpCl6VZn)$DmmC$@+=fA z=_cx!VCZ6554n{ekN@*&7KTOwPYe5e|PFTffQvI z^Pc*C7|G8>90`bEvs_F(ggTa8>u%QYIX|4|A`3#H{1fPuCE9%Axsq=C?>*l|qE*YX z`@(7q13Il>YLH^_> zlv|}LN!eT8-=idKtCUKfLR%9V{ce!?d-5{`EeO;79gL5EDxCLPPXroh+XqJ@9sw?$ zx(V$wt5~P5?^$bQ@v@JJK2Lb+kl!>>>$7cFUYet5JVyZrqVwC!`M`X!4&)`7oKOOr zj+A&=5Zru=Q%_LPGnH>GH8`r#mjX+61Gee9?&`C8g;ct0*(wsm41RMD z0NSEOGEF#=i9tlMJIyH)Rv>CGZg+zp4)0nQOlPmo2}jh@E}z2l*!9k>F5kmpN_dlS&-=&x(_6+CSU$>#g;yh8Jak)bM|F>@KG%K%KAFy{~{6s#QOis)uQhIzr(mC zQs6{5|80RNgQ;P)(`CK8LEVeEL(*1b3m_T>IlM%B;m1#Oo3#HnCMG`7t+9$J)vf+3 z?7LP2sq#K|Oc979L($zbgoWxt35*5MFI3Eh0$)Uc6Z7aU4FB+kcG)slR(5v2fvo|w zc7i{nBfqC|_*AuBE^pp^^4Ni0?#A7H@Z^ys;MwONp`9(6YUBRU%$zBJOA8-%(xLC< z2ka6sGdA4#%nIUUD~XCPbN|I$t=#UDza9f$!Fa&SgLH%4PA{ zQ!4cWqdCzPKadOw{waKSritsmNYX3)7pU+OLJqT=5qui{3^2z*Ul4{sCalz>0iA zCbF_7f*R(-$`j&v-2pJF9qiMmZ8!;4oqV{;RFEE9C$FAV?YJV(#KOWg#gQxzpu_$l z{%CpRga*4$a_hBA;p6cB?*>TE?)V#{h8hMTvG9Z4eMeJ7nVmMOYN$_Kx!3 zWkcUL14sGoHSc=U2s(R@U?;s%|d68U` zo!4Iyu-L~pmtHp@>(j5C^E0M9A9`89Pt})2^`w!7~BLtB(4e}vyuYiGw^Iz`$)4HHo`T*)>iJb9&0HZ&_8CI z{@iuNoJ4hUZktCNs{>~7b|~)thrUScFLe#I&|zDgl*-Xa05-Q7s7b`f8i}wj^AWd@ zOp4j40aimshSqQyhG+cNS~^&b~;7sAiEISiWJE79S!)evpNYN`#r z#-v+r<3jMW-e$qVzUc<1zAuQ$ZS+n?XNvx6ax9N|Y~kIqLN6oC+vTP-r<8Dlhc;^O zsI#DwAKA+OnulP+ib(6S5gX$;S59SY1?ei+j2ihrr7)=L|IM!A_4Ke)MH}CGW^i*m zcR#g+_iPj(dUECdph!S(Hxuy3pM}B)zj>Xes`B#I&M$ZM?d>enUu%f&2bkheNY6T{ zfKPUnj?OPqELVG;&CYKjk5ykX`CZ#+j#Wf;i9ccQkGr_Y^Yd=c;JVe8%^(sqY$EFT!|v>UJbBlb{1*vRNp_ZrvK=NnWDDlfh1h{oq$o7A#U; z?JL=JdUoVX5m$RDJRCVNtsd0)U>g~PXk8k zOs~!@PvIk!t&}e^O~gxDK9u0xjM?fCtK#RU5@3FCgxYg`j$iYWamNmWbI}8rRzxgrH!$Wp~XNsCq|Cw zZOdL6XGH^K!~x}*Ifl@=PVRVPt!}3K`_Dw{&>e^c#XVyAH*X9l=hZO(uCC6+f~EVm z-n`N3Kf1fh^GsSMCnKNM@%Cosh4_fP&<>+|n&m*M7F)Xu!jNXf+l$&VJ;sum+P-Ng zA)POw|C6ou%i__J(<7|t4Fm6kJ5JTyL07{`lnE8STJ0SO7Gs{==8gR7mEsc0{z5Ud zBMCy8V(LGy>aSWD(?@(M7*#~;bhvEt`br;CPQQqVm{Que+L^tMZD425vbdull!cu< zDcn@&8kr)CsW7FIiyb4#-=lhcToL1Y8Bt?9vh|s2Hu;#^p5991BtUXRx4K)=<=g1s z;LzSz{sGY@S<#cqyAb0v;f7qDv+-ktN(0x4G_PN>WwEs+kr$H_mIH}6lw@8Bd4MDv z&`|1NlxksL&?mj)!gsj@968rQlcBPC0XlMWnGoNS1PAh1R2>=+Wr{})vw8`&7B@L( zkMrk5si%s6x@$IEBmz3PTgIT(*Ho0sjRSg+RKfMW$FlKhp@YtSBbN^dNA+hpwaQyu z-&L&Dd_303^2{B0Tz2gKLrS{7x<7RWSsffIxN|w{R?2F?KOFBY9~cHBhSH`tbKDh6cuY>unQkfWwZy zRNqY%b`X((sm*DvWo0dvf~g~>{ru2CO|31c{d&jA_?p}Ldtr@VhtQsP|5dYxQx3$s z3~6;%c;sVTYh8fLTA;{;D#QF2sRX=S9*3`2^L>P^{jGp}nhZKPP6`bz;lr=s4)@-! zR9f|bCYJS8*Fx&FH3)e9Szo$eueILrq^gZ<5vkyHPR%AV<^&M@j>Co0mTSe%o&9Cf zr6{$0d9v%{W0JySbCPli?^Z%Run*|j0IetSW{u6OLbCk%=Db~77jn{+Pd~9Y&$9l= zx9F$2ma`Bf3+K(r6sq64Tp~64(lw&;aQaBEgmMm=VWiK9RS zAXayjeZIgI@ct!y^wjhPV8iU;T4I^L7m5NKxs65x6mAB6{upLbQWidDb~BTWb1CTHlC1Y7M)`c#>9kvrL!>D9JRJ0ERT4`k9=X7C8*m_t?! zj4^T(ve@~c>Ab{QCh~jZKs z-h^;PKzz$aO*=)Y+4V&sa?U$NaMUu@)N&YSnQP#;`N8{Q>S_t8jvh9zIFBq1L+=kd zkIt|6#Gt=W?)-TuS}>0&-A#Z-f&fi!XYaYo$Q=?zpM?hx`mO;cBzo=YE(K}q9JcI< zylT((5fi<3{vH1D`sQ@42U_nU&!4{SmRXQR&@qB=p_xxGmxWmm8bijUO1|HJMQNz_Pf;;9RN>WY2*|4_}#3< zi6q1};$_)e(P?b0Bb?SQ`r0@xGdGe}dU7!Poo=sb8VrS}u-;azu0dlPIO zKS${e75}UX?k>*FWeKY5+itxW9a+ht^6?L_0#>Vz)(5k%#l=Q<0wf{UldD*Y5h6Y! z`9N?*1TBjN_8cOR8K1JGWGlnG*V&hHvV*hX+xl-N1fh*RP++6o5-S1D;imCghHm6Z$r#TsKWM6djeDG8^ZNi2j4|`w|4<*J5v>J7) zLLg-gZ*nq*2fxb!Ci4_4W>Qn9y;GKI22SJ(G0|01OH$h(AA0TZCg^O2s8<5BE#|ePu z`_HbYJO^|zn)_{OiI7pG@S$c=S%xX44N1DTsB46UAp@5s>`$|i1{Y&av20m-@9#_z zL(D;9;xD)~T-kl~IQN-9yu?pPAmHRut={ZQOY3dn?LmR(_GsxrtP)JQ$v54|@hO4y z;cI1$bF7DQAOHaHDX0y#B{;-@3-CHhbwz>F!MrE~6ba>tm(Y9Bczrp=m6?dryLnYK zf(};G*Iy4(#xYy^TaerAoeGf=QT@7(bnz)2R;>~Xkya$2oVG}{x9Kbj4ZYJZ7-sfz z7D>7DkaI}m=>#bhR;E-5WN^3jnK{IfS@b=&cZrX-nUscvYZRDD+ zPEKx0NH3q=or2k2e#8ReviX0Zz}YIF3Kz!AQj~oH5Txw}eQL`6`W1e(^47?RQR-b_ z$KSs@{R4)5Wl=)qGN{0B_8>9^a!K6_|128MUOzL&wBS%Rw~@#m)ww<43#cU@P~_}b zf?Q@?Z5h{75Z4)*CoY&t8R}622df;7PTmPPd_3 z3o4}N;QkMH`3vhGuqCsc&eR)?RfthHxwV>MXAM2BR8U}4pyt?LAEuyCvdD~#{A<*6 zGmo>Da%o#wdeog_d6D7&VcwU1uYqwHEBei0Wz;JN$)2?l?qyiZ)be0 z&NwyA?G!7DJUzpyKgMylL6)FovXb52OCQ#JDF2JQb~lPZ26?vhTDRFfpJ;DK_leF0Q=Ten7+FZ=$K(?1kfEN&Kanfj2_n z$PTW3t0DQoZj=2%QF0#tQB%juoiIfWSBem?p2StDF#(8rcJs=skqeRF-IaCMhunkV zd2|s}e%=w)V$~PhmugaSGm(~AGb`J&ioud;j$48?4JepOTKK1lg9F7Bh>UNwFO3nX zMLDr0Bf5H1xB%A+fEGe%)DK-tM}7ZqB`nm*|2zNtUzJ%=H~sgdQi|7pR+x#6EfIjx zP(6gv1Q>viL4E!ox#(YZ;9A?uTA=*plm?VYf%Zx9?w%eL;ieDJr9d(lyQUr9qLPxh zPY+1DQRUFEC$Bi8Dq2qTAP`7QOdWtXRa3BNm!hh(c>kU!lK|jYYUK^9<%-v<8kY(q|C}1gcQ|KGm@a(pmKL8=GvJdzRO3gGIwd zGn?$%Xon4=oM%MrXi#cZz^9KF2T=_6fi*Pf0yrMGz)HLZf$l?K<@JtvRwk8Up&x0IW9a#t(NBAl;*tUO-{e* z)X0keVuyFaWmCy`1f*_U9Z)p9Gq_^_^A?~%dH8>gE-3I$1;X$?pZjR!#iZWTA|&KH z&{c@fiYU`(L{-~y@hV^!o0)AN4i^8cuCY`!4=Yw%k1}&b2t~tJHk<2ke_%&l{P_0m zSE&wwho-}i*RLA!>(o(IW5fR-eec@E@Xt8Sr6hW^MK_U?M|u$dLZw=J0S%d<4XCtT~#n5w3@Xq*KAn5O6Y=8zIL7vCmIAEh)?n`+x8Z$MxE59$=`R=4Ru zT{JSmEMKPV%on9>=-cQ4U^r4zP2o4=k1Jh*cAaY={Afi(|B`tOl`OmFuy)BV<9^Pn zFH+Q(?V_5jIccU>LOtprQ(Mh=Wp5na9rrfjdre)(P$%3zYFX zpAIGWU!5cST6jN=JyI=sew^Qzdz}9>n?gXJg<@vwJKcNBU)bR+I_2BCpJ$!VFH$KO z0O-l&8`^jSD)n;)!1@5=&EbH;khFDlEPu%YjX!U<2efnyM@Pc-WE)`nzaA(hL;SSK z2{z}s$SpQaxLs3!;!&F7)HNKB>G$z&HaA8zQu+Yz`hX0rP_-R@wLjQjCjFKsYi+!k ziEBO8Sm@bdXN$gT2vvvk!xf90JcXo_-im~nN!F>VzyYf``wW@2k$vRJl>I;cEpJh!vy5d-P%u5>Hqq+Sd!Xy#+@r7uu`?n{ zerZhzMGVbxr`WAI0QgoX9^!S|xD*>5id5cQY>lh_roo*C{8SYkwHV zYRKHR_&y}VBmehmf5LB}Ii`?CLAk`l)Mx$&vT4J@yPng`AP(W#nWjD^J^eqo(@u*` zx4yW|C#m!U0uvSP9BzOW!3=jJ_8B7T5=yaeCG?^%pT z3QBnaxxq03R9E7M7-XV5X9k$%6He3e7~MvEs_o&#ASi6l1%{tnbZtgOkDi28tM>ZyGeW! z;@S~?viBk*=)I2&rEHPZ`p zIr^ADYa*+zo^`5OM@h+_jy7%($0jOSAs8Cjp2Vd(#soZW+glSU@NI$ED`0FO5Izrv z5%5c$-2>!UJjjCpAD*xMak|_A2%tdeSjAMG(iOsxF+=Q%rmq|Wcni)f)l?|U+?#Kj zdNzUOF38~w6=!*#**D7l+Vgmb2{f_!zgB7mS3|*tChkdWBp!>n zxFeQ8h(w0--ZGPEXrlU$f`%=Ke;f6z;M>(S2FE`nfQUJaf+||Hg%uK2@8W_!qfo#$ zO|ie$HVlq8=jh8;8r$n0Yij>>Roq+bB1W3MMHAR=f73b{lD;6$OQlHYLC-}2?r{9{ z7%Pog{uxLp0yKpI54U&L-2Ocz8zy_%gbjNB1U=+@x1qy%7_e3xMjO;#v!AJ+fZ7&N zovuXbr%`_KY%Y-WEfnJhR~EjxVD$48)#Xy z?}1E~0p7G>tr|9I6LwIdz>}qn6Sp1x26YgK4z;CUa8w{^rn%*S8_CuPU0hG=M>0PcaLXb}waUi{A(042QRXkb&on-UnZEpjLoE zFE3Aq`wT>YiNY!YnFq}?v6BL2aR0~X(lqMa|I{}`WJDViZN|ye@0Yvd<`;YTY%g}d zA5#SNLI{XVuod{Ns7fxl*|&8pe?`Tvuv1l#C)wWlRx$tP$`^NR4BWx6_k@X!S0Yy# z2N-MOfRb)okQwHm&dh7|^Aa&RUyc_cR?^K~ySS3CnZ{xIp?+un+d`gIST{SRnbzQs zD5|NQfYpLbBrAWKa*`9x4NxT(;RMERTM;QnfAjNz6QL-DwMvAO{lGeAC3hy)e`X4O*4z~!cL8c(yzn1&0XMQ3z zAa|)TAisulqh+QiAVCk@9gTrv8^`XkrbzkB%eiV~Nh+;R^&z<5}^s z##hSZJqirxA*Z3sYmZTHX@jn*{eg!sCE{aCMf7p5B>52kd8l1M=G2?9TIZRHOan| zK!dYPt1;nS^I^e~x6Ji5a-}UpxX(v)crTo>EmKCawjK?L)pg$}3=rOKKh-S>hQC;9 z`K3rvDKtb9>gnB3QOA9`K=A(9@)xT44G#kYSS6W`EnsA>1X8X}buu1RP6*GDOypHq zhYRr;Lu$8$>*Pb{PT9CdXJ;;R7ZEC%7|ku7-g0d)xE(|AkLvy_{W;z z2B#>+mNmHpml}D_$9t#k%F}X>eEyI)exGKCsWcmh9wHfLDV|>xz~wty{ZuCrrE#e% z?BM1yt>!N2NpktD;JBFY?&Lz|!?&;EL`Hh@=G)igXlp}1fPjC<0Q`FgZjp;p_|^7f z+#pHym@jg_b(I5=o}9xx8AM9*naH5EHoYQu&p@yQtj6ygQ=Rj+Fw-2*Sp665JNGA{ z-kgup;#bNXNsLQjFvO1AG&_%R*U0Sf)m;xoDB^86kx9|yw|%d|dqP_aZjSPeOSr~>|TVMu6i z|0FASk8U{2d9_bP{{1n_;nC!)s{PhmLr(L+_A^pH`js@-PT!D_`jWy*#-r{24kN$* ziD1l1v7lBst@AKivm3B8L}^ElWSnT`WI{pD;+SHme4FBt#?3W)F6N zxmU~Z$*_ziA`6(XKFZS{g8}>ukQC+(x zL%i8_5@w4nd(l2w=cKqw6+}$TaaL||YndMM_ZgDE^sw*x$@|tjMx5C&(`z((PBXviatBHY86fTR`{wdS0q`e@on~v=8KlwVegX7Nm*z)8BfF8Ga~B(w9QXCb zczfi3)IAK8Qb__B`>%bDOov;zo4q@3wWZI|1R;^~;fR|lMV;v&lHed=tP@68Ouzrr zO)dsp;Y$vk3pN}t0w z9*_>vqF1jH4}aZxs(4kWyh;cO4%PUn}Ur81^J=;B#CcGKbc zOGG%^phGL>tGbEUi7^a`CX#%?@bW{E@4L@IneN($Dt^@_h4<_Mn_3luvR2^=My)!B z8B_fT*ATxcqZE(Tsl4;5H-s;lw{f5FIKVgG=wobLk7%f0U-8QQr6n}F4y`ftt^-3y zX(Y^MYnX-)^AzZB>UjvSHmH{CON?Vff6)?&rVf>O@g(kM6=mYd=J}o@t6n`Nsz7q= z21U6jpN+fsFy?J{J{8eQ5lL_3s_-pVJU=Ngn^SmZeu401YKo(=oXrK=!{qy*QQ*TG zJ|X>R*hBfI=1fFv1)r9R#h|@;BZjN(pF9fj4tXd+rt5jvtkOzc;I`0+ol3>I`bn3a zexo^KM%xckDtvU~&cBuFlMCHF#8hACiK|6gn_0LGE|+`4vz97$(P^0!t^*V5<=GK);1MCbN8II_yh=h9Y^KX<^IOwk5jaGBG?Ea8{0>I&U;13 zxsX$QaF)Bmk^~+~-w+weO;gc&&L`{Ydtt!n?5&y__t^3&C9cdzLJzA?n(2O%G{^~Y zjvAMwnw#Y)^=g+WdZE}wmVNNj5%Yo3jZx7$E2 zOIYZS#!PdsFf9eA;*xmR@$Bd5GQ>Pp->-b?_r}GAtHgyW^zTmA2U|cIj}e@ZCEt+G z=_CS|aDg(tJ6Y~g%M@HCmUBV~Xv~?2UySO9%->g{Os;n}lUX=UcBk1dZ%+9L@3{)@ z+2};-UgteTb>{}7d%VNF$O)d**HI8I|CyT18^paW)=U4&G(1saHB@q-@0F&BJ@wnI z$7gtdjPB2AfBi|&TV662yt6zJ;>3k}rEggdb}d>bD@?!FNruk3JO2%^*qLCIyJ5d! z9e%=-;3t?KDtuJ~mIpyDQ}KLn-PBtgBd|;}hGVy9cU;$QMtM^lcMOMJCtnkuZQor~ z$c>_ot4xl+HLZ4jA^67TSs;$SB1qSDNQrRADj`u?lYoEVvdOYu>+?@P7;~K6Swv8d zgr(`?Fl)I~Ih{97AmMnWY%$fD%p@zpl}liE8l}G{K7XechaZwc*yf z=qwGRd#~4#F0xw1Om#FF;2%G6;2;QiGk~etD@*Za7E^sYD0(p{YF4yL)Si~93#`hV zWymNbb}9BboK0lfmU(!Zc|>wJ?l**Mb6jY4uw^@d)UIH{X*fOep{t%b|BlJq*rB%- z5FT!Pb`OKl%!|+4AuX_hNll@7)`G#MxEW>sbr0`|nwy=wxGR?zMg;|4-zhDP_Tbi# zvrUSsyd1U343j~GNO)MsC{|;ifUk!AS*>kYo!n<&n?LTzKp-=q%!8}@Gb0_bxD#Yh z;J+t`vA!xX5;U9X+Wr5?`tESH-uM5img+7ot=XXzMQg86MQv(tQLFaegsP&dwfC&O zO3Z{Hsj4lpMMS6?F=NN(cl!Q(|NG?+TrO9f^W;3|-1qB#4SV!TI&NT3!YE2Q&B`|3^xL_-Dp4*ulJzOiQSzuH7j@n5@~ z7WcO%O-Kf%m2vS-`b%HL!L@Dn2VZutqU&)DB`t>IRk-JX8G)m=Oh}Ac>e`nzv1gS% zn4GR;WRSg-PsVlqv#BRf&kmwNvKcc?t4>bw`8{Kxk>6pD%8pLyYM!xo#PT| zrH`#?Bvv<`(AlBf#09L{eeVAX$#)C) zF0tRC>2VxziR6;m|2fLD)Vt#AEpb^zw1!uBI(qA~{4JrV#*5u4GE#hbhQI0gRo)Hr)QrrqZ}FdG z_Pg;`yd#=(oljK`|9So-@#4VS_a%NH^B#V_lurH-rScf**HRRxX}B?d+toU#7mE&2 ztTQ_~RZm~}pvuuEq50R&3?}8zinq4+oVI<+r-LkKsijLPH8=3==cVb8Dm;<7#esMr z9CR+gCHZ&HZ({8)Hu&*0gJ=e~dgZ6Dkzuq1q+&5&7EkRa6KI5Vinh}B+b;lFfsbRV zQhP)pKsj+Cj^#h#lFmH%9r99USgp%KfW5O_yx&hwRPezAqwNEk!WXyW{=!4DOOLR zG(Z}t2%c0K%PE8?=Os<=c$Z0>J>z{eN*==mdw&yR4fHPfbZt)i?Mmv(MO3}1chz#L zpx&194!f!TXdOe2yy+x-W7U^H5v!TO9}o!L)LNFl7czI=Gv9KOLwp>SCgeI1up}_~ zv2~ww_%U!s-K_uFhA^{ozTkP*oe0aOUNGRns(gQ;S)TotH@@Hf)`zGmUYQ08Z&|q# zFCUqbQy~%5`h6~1aqB|+O5-153pcdgR^@D~Q$y9Z>6P$UnH--{2T0Y?@Q(cMc%^>f z0EJJ20)}4L_wok`1tIp+jK91I#T}e%wJygy$Z}d`((>f$XS!aP$R+eqp!3`#)dk4_ z;Kubocc-P|(A0sAL=R5Cwa;BovABY5iB%71w-wc=tsaXG`GAgZ!9=aXA4_|jSs$vn zA0!e$t9Q75aBDU{zp;^E{JRugN{wMdzmn=u)DVP77MqN(W)8B@uDp^|=N(2i%lAk2NzZ8>45}DTGjCQ)#pi*{SqcyQsxZ^P>mM7?twkil@p(Zg4mfAjIP2-q2 z)djC4z0@=nn00?`0+N7)ha0ckxzsdz1)UpcAN}IkQl!;>P9uK*nreffNx#1tmOizu zl{YGMva{JoD>ZZXf0KNnNuXf-K-0t4o%lFJ*5Hrut7kTEqUm!Fvixg#Lu|8?jwnH~ znRD;B{QNVo>)Wl{Mg^MRnoGb8geL~X64sPY#Z{l?F!BXZ`GIQQqjVul&BKSg(H;-) zCPny_h?8|F6|~>#v)hcUS`Xkjm5gu?3k)tQ0RK@d_P>7qQDLWUD3@?85!P39gh$;z zU!=kjX`=WW(lB!`FMH`oUDiHH_p~|dh>hmF!#kB{T>?A+}|c=#MrriUb-Nw?`!+3LM`m+NGav&hXs-9RRh zw44|u1z>vjT@OA1SVL!PinXr8hRM^mQ);vo(@J`qepYp**R)d+#;gP`#n;F3Tt)p@{c#Z&) z&8Kn|F&xdUknw}9p;n^>j*nuy%TUd-R~;+*y_&AD2Ex?k8b5$xV4svxza6!vV$qzW zQ`-36^V6{U;G^SVSim>=OcS7oR(?z?C?i_wXPzPLo~U+sXjLo8O6%(TMjV~WpXt(K zm_J+1Zuv4w;W4JNB4Nn$C}`x;q}xT*hZyi8ipw03svFYC3 zANXP1+LVH)GdnXUHNOp<;}Th$6l&G6MPc}P@D6l%H`5{Adjq&>(hI5U2|aW2{@6J# zKdQ_cIDox(i+uA6q~_CFtoN?&)s+fw|E(%%eqgYVJ=RD`n33j_{U$)COjHt=oe8D< zd)zKtwslkpdXPvTYjBc_dZm(bVr0z7Xd@AQMuYBvU{6 z^;MOGq}<;_Vnoi9RW2T&^}TSNMIC4le>M&up7z;jD~BifpVICZLZ?T*w2|daZwkSc z#!^E#vcU&zQT4D)tdQ5}U_Vs?p7k(O%6+oLx{J*~`Mq=A{Fj?91*<dnv&Yv#*Z|xrB=z>V^}V{j|S5 z_|%Q7l-uX%Nt*)BzWqN9@qL4BTealh39E&BZTAG`7-CiAp8x%1*pqW7pKzj4J$n0= zYS4&A`rg(?PpF_~+)2f>tGVM;t&Vxq-@xG^=A`c>juuJQkKtj76Ic6BdbPO@#=F=7 zz5yEQ5y>871Jk;t#ZrT-2yZvpSSEh067e(BQ}7RB)>XyIysb>J@}qzIH$ z5c%fD>Gre;n9L%s-U0_b?7(dCEq(ohr7k!p0lFz8&&fby#A z`Qva$c!n*BWVUs8@xLw?1Iz&?MluC+)aBs0@PD0YH~o%(q>Vg=f1tA&qfhQG7`ghp&Lfd|Q(`NH?jlEX z1&KTB!a9p8YRnO-w{h@wZR0vQKm#ka3@Pb0L7lvOz&oYhzy7ki;H_g(Uf1f&w){Yg z`<1=Sc1EY5RF%|q^OCB8A6Q`&RUxDj4#gfB?^_fPSRYe-g%m|IsIJ8;vKxW9Hb zP;|0N+fV>B{4OeGW1PtHQLO45i-D5*>GiVh1=W^VO&Mbw233wiaoVKK5ooV$)MtRT z(HOOHfA%I;4V99ERXwS{^#*?zj~q9+l7{$kmZO@C&|s2A5I0;~A+6(B1hpV&_Mb+R zw5bd%+E)`1-QJ2a+JVa4w5q*XD(PCDR9* zS7Ayw?nW=Nam5#blQFlDa<=mfsbV zC-J$!!AO+{e5d>9Z2Tk7qC;UL|*qK(Nz<1v?z~x#$Fd%mL3bk1;)i z9y>CiAxn22m*MzVy8$oj*hQX<$_c60&{kpB2R|f%1B)XH6^<-8=vxWWpgCFn=kN5S z#5$|q6`_gHmlg$wN}kG2yq|DwiDUI+E+FHy;^tu z#<*9yL@U{#8t?%VtmIQ=!()e?3;3+QzaULILWS{Ad==^f$(7#g(AcOHuFBt9k9A}u zFT=P&^kpG_UM9O@t5QeOOYYe9+IwF}&JA5t5jo_8RjcN6iErziui;}6P#;E+zDjCY ztYU59KaT`}pU_X`2MN~>IE7+Y|9v?W;a5UGof^S)!Gw#~OSJ0fTS?ely_LOITh9MT zC&3Or2{A5kN!LM}LVZr|%y)(eb&-lC9)6Xgv3wQJ=#Ak0=GNM~yxYT0auwGA&$F>r z;r-wu9x*00av{`Fzo4(a%cP{{*jY}?x5`SeYSgM=LvF>r=jlJ`Q}6v6fPUfmv5=$p z=*>j%1*e3n*$hw_Z)QC{!O%DPdnO+d5IhRHeytkG`2@>w+b0nqmPBI(W*mw{JX@D-%1nrF(unk{20?fxqJM2Tw8KSN^hemzHBd4@DD|_q+_T`Ax zJD%Hnx8luUd^#b@>uh%cd+`7@FA0z>0!4<@QIxVqjxG{>0@sAfvUc?1Ku6{)frD88 zk6ZkJRfN4SvQlky44|C-^fOp7h5pW3O(?9O;p}9svZ@_eq%FfvkqsR`7g3}nXFwZg z=#Isb*VzOJi24Ax?`!fq)M$YzoTh_@b=Xf`lI=qJyh54RjPQrig+i*YDxTk(Jc${B z&R?A6WSSvmc+2Jt&kX~_TF;_3X-d9iWc=q*)+tN%v2$#CrhbXTW;$u&I;`o`?Q67F zwR7!iaNVx;a96;^<+f2e0@X+Ix4L)fK1XFEOUi|=Un&uGPTz|;BX2P15F0qltFSqy z0otofu5h_j=uSz_b%e9XWq5WOu+93g0EPmm^^w_?AHCXzJhG|p!`kfabs1I&6Gj|9 zXOhy2ju(Hj(m~KZ8Ra|nSDemY;7-+jx0~TQv5{4(lzK(?l2rg9G#n2d`R3*S;@?Gg zVX61R6!ws0i!ZBxT7(u(YDI2Te0e#t#!hEkTGUr9+N_b;G@)NYT9e!PJlgUsnLy7X z#%VT_e3Z2wIh;+iJV5eZ4&D>C^hK;G%V`oge96`ok4J`x>xi`rqP{Z_coUYsnq;aF z0yFAYx}T_C?Va=lFEmk%Jc^hU?bgREXSzyqjwNca;fq@&P{^i$?Z)HlgVu(rMCm*) z9AFf(LDTM(Ud14L)r2b18*d``5hlR2Fn|H;$~!v{RWh*ty4@=MqT3$JK-kY%%XgBO&P9OGS(#muMHyzA0{@n*qUe911;^8N?gCR;zYs3nvn^4Y41aq0Br;h z&3a3~bn0U_A;X^BRKULvCdx8$VuO8rq}n|c_^ zYuB$aHYyvuFWj&)98prXVo$zs!G0O#PQEBXn(`H6m>SdA^!v0Us?QIT%hnodsVOX1 zu|+Pc|IL*$n#_uJZ+!vZx!9gZd^;e%l%;dF;!p-U^Gn}Pfq(8=O>`6EIC|+3v(uD! z_RTd(ipYFjd^T3Ym3X-})bq}TH`Klw3C4p$rVty>cAD;~hJ8jiHo(! z*LsC8y(NKGdxbDgtXuFqp_cl4aW2b87w`8nx$eth+}-Cxs7AEG#g zC6{@wGk?asr~c~hyS_LMHcyJtsJNG$@f0WPxAz`x>Sbn}Z0A`#x=>6?<-NibFE^CY)46B3t6$GOap|qsW6DpVLtZA63ji&`S zpLi9)AnUoq(tL!~p@kBTITyqYk^5uiY6Lc>l*!;5P7k0lp9`ImEmvTBV++o&E*$NKJecIq&8`@12@P6=QQ>O%pBE)33sf|7!$)Yo ziBZ2a%!a}06Nk<^PO7mwuyRes;j|wgyhcRU83*mVZNfxAG`sQ*>V*W?41m`< zqH$w>_uB}a0-b}TSJO%byZ*9Qz+=aw%08_3il>NEU3TpX%zmO|wv@`#=P*6z8N$?L zxs0=sZ-h^-;>pOjmU(GDiQFoK5x;D{1~p^404B?on!#$V3U=q2w6JB@HdeY*1uO*C zW&QmH!vzijUpmB>ItgW^I6n^Q6y-?c9@#CQZZ%2SKfpSYG`5W>f` z+1--vzrBrVY?&22Ug$rhIK*^uTeq#fPM)TGn^_`o6@(*wwx646i2$l50bAt~tF#;gkh|KIp z`{H+=R|x!gy&Qnem&vd!1X)mk3>`WjlUmFH97<*+uM}zX-1R0 z0-ZUm;oi>~cg|NaHSp>+9(>CEdOuLwzy;TNT%z93u#3 z$%!x7*q9Y3&DWCo4S|d|L`H=1q-d9D4d_CH&jW<>WQ|=2z7&d!FYr1g1@rS({_$c{ zlq~&PrP!`#MznY%KMc0s-x|$ykqqks)Hvx`KE6#jogap&Q6Ht{{E_w4_=g20oVRFZlS`qrYjdBQzN{}Dqkxin1s>S zokB<7*3tEWJYeXLYw9)`3`JIyZ?duUdCO>9!Llw)D;RMTZRE72UtE9fHRvX+p=wc# z6dKY+`(G(3R^pnZf4;{su{S1eSKP;DH?0PH#I*Xr&YVDX8a{g09By-Zv}b?(1K{hH za@J>uG#*D!jkr4RP8kB;acL)%5wm@nGT_PVkWql&_i@TIwttdjYNsiEWCzUTM7aKv zDDN~aV|hKW1=#{UG|UYNoG(9xYmiSpe{!8tgdM_TggUI_+P%f*kAw)pEucubyI3a76#X9s$7lmyBPqY{lacN zl9QI@@dJ$9sZsGC846!y9xb0T$PLus@C}jv!<^M9#^BzV#Ap7zMv*n1ozgPK-n8p0 z7}u>KAUw79D|^Upp@_c2ekrZ$i-(whOrS~E_M!Bni%QHYA>#GvLLommMb2iaDCWD} zz;Zg=K(uaaZ_&Kx~XGprD7ne!bodo3=}r|@_ehi)9elq1Cj`Zl&VR) z8JGEMtWP&;1+lQuUbz(D%+|^2+eqs^O`H_>{7r1xU&)1f_hM6o`mFdP)JMjDNkQ$0 zB}@ZEmMsDE$wWHh!MsUoO5gIeFKP#sdB=T*!t(=H34VUBqT z4^g6Zz@@mKmpWyp9)9TO-4A#2d}jSov2t@r<##Il)4a@veTq$moIs#M&fR-{wX{;j z!`EDI(2H(-Q_+hus5-VNGxgA~xpZPicqQsI!}pSi9}W80?OsM?_QEvXByQDP#jews z=~u2MK~HKG^jjI@3sz4O7`ink5>+ZZ<}gPh>oL9YIziiLRc^p!9LajNHn!Pyc)ICh zu;6hsMl&n=KJS%>UDW?nFYo(r0oIj2?X39g_vk(N%uATPlXMS*7BBAO3_fl*G@Hzq zrDk9IgZRKK!!x71IA6{r(E17cbLVqUu+GUr5x5>$GFUIe4ET8}FL$DG>jh7jZ-Hbb zyA5;BE4)j>VmCWkAt>kEfTLn~$DF`O)7uIUR?h5wat(13EnaPFq85|0nCibrF_LP> zcabgGjlSv*W@u!)|IlwaM6@@l-1cxK+{R;exkJDP!z@Usy212&Wa!alfB#Q1bC-w! zuuu(9m^oe`#Q|G9oK2Ww>z|tv@BpH3e_=3YK(lf^4p<_ioSFNHR>jo)-Q!J&Zr0BO zM*F0&mroRdVem(b6bgS3P%nY{pFL z5ekgsJNI|3a3bW!6=YR+Md_+N6dV@n96}z#)}mFEk&4LOP;DSt;%P$e%wDTs9bE)` z1J9{&c$gJtb(i?~jUe~`EjeJ}k@oW-lIc=hdul))f%aQ(7M;ct4eNO$!VFncuV;?C zms+rLvS=w9f1l-i*Wq>(ii?l8?USjyMtl&d)kfRo!HZ<+#n+<9 zxi5bITUQ6zoNr9fHVvCU)Ht={2e^x^iil5S#Nl*=4aKG2X-Y^F_uqAerqwAZAD$d` zcyt`z(>7fagbIt#YF0$dc60#83ao19#f#lrp{w^T*8X#Kk-O{5R}GQQZQ4&tR{jP& z9_OUkqB-V$dGPZlD2I46b>;LqQ#(hLZ9qE6_?P0nSk59uKTjfei^s;7xRZfepTngG z3LbmZeTBi3LU)Mt*CK5ODxfOvzHITPUa@>+=IfGezUF$#YNVTi5)zicZOYlBZ)1}E z?w-==Jc%Nve_8YkM)rN!N_kLz$=JEn)U1hrN;9 z);?xA{cdN3=z6$nv*#>YVSnk}fUK*3)2O$>OZa6gkJT{rc9_hij*b1^`jrnf`m|d^ zked!#xI&NI1Mbt>`5q0<>#m8KEzO3=gENnG0smTE_4LP;H*OSji;~pV=9roF#26?n z)_U|cJR1K2a5(_7_T#Gr1n&%2R#50j1JPS4Krhq+Nmjktw{FfwcxvYw5YKIMc-6K8U^5oN_q=kOGv-u<&dzJ(6y zvfLzmo0NwF9A5jIN7%-HI})Whuh_TW-8F3l$Q73zxGE}B+=7vpeAoXnGuwzUh%->k z>%*X;dkE|z`#-N#z>cTK(6j9_L)u(^73wVD0XO=}KQ-IVj+@|<7CwSFeg|5V^Who; z-xGcnXUXRUZQ( zjp0o#+U=rTkzR%kr{o8ia38aV0o}o`5zIgedMALaM&UkCa7 z4>NxAr~zFCFwQ$y#X~i-?K=X_u>0MWw|vv$#~O@Uk&#wSiLRBsygb|8TY@|DdGFh| z7b|=mF#LxY1#M2^pVD7V-Az{>!vC3zRv8GkN~1qjl9`+|1Tswh7m{s{g-d#p(9fOT zB``YJ3TfHF^1GZ~5OZ1IJKxDOipo4>&}r`i^hBA*TjTYHbXY}Je|ZvsWy+#U)GHg< zaV)yaJ*9n!mGX*9k;4}>VvYF1#7weFMNf?eZ~%fjc%r@Jc@(@l03aFrk9JmIT)t7T zv&Z(0Wjjl77Se)w;Ce&r*^9k}DbE36ViZ;Cr?{WLzoYel0w_Hf>-?>IKX`_(3)7o8 z@yyj&%+Q!ZSNBhHzCdme>6{VmDNHy;%x(^QR)bhYWn?zJnx(cZb!|1a`tI`xFvXo| zjHMh~zfbJG#kO?h@lxyU!}}m$zdZC6>TUw8`_(Mc(A)~GF325>q`HQHKK0g6c~_eY z3jKUHVCH{oF`n0@CRnRx?^C=HZOco#JuK7)o1FtRX;X=;c=B!;S9Ng?X zIj>cCm*@Iq0|)@jN|43J`FOYGqEzzpRm5u5s!P@D<^>K^?d{&WX!Olr2C+cDt#JDT@_H69Zz50u1Cg%F>0hNn#Z_=u?~0ou7+eGv4sDPFv)cM!y zG*zI~?hQ9l5R3}}!tJK+uT(%uV?u`5vXN!-z^%}4D#JI#D5=XJq@E-*)?G!Xzm}E3vG_fx#VIO@R$Mb zL(&D^5^pxq(YmK=dL*p<*?QHwp9-0h|Fog<$WDx1kHxMn#Rw!Tg*lf%iDy$t?Pqd+}f*3f1I^h zi^GtwM4;xp_b|x0&2p)gXUcv-9&4PwDn>P$wXy@@Vb1P4VDXL)!`fD zjH&F^>q$=v`MPXh1>C3{Xx_QJFYE%weZ-_@bnL>xE~M){i+l(|Kx^f|vNz1!wYiQh z^Ul)QN^mn=RZfg4Yk5m@_7h#dfDX=INAOADb8S!7=9W+odf=xWA-I!`J+RbhxR>PG z@elmFl93gI8_CX?A#C6~qIaG6;43_IEzo{I6{^nD;NkRa?do13p*0YWyX`yaa4U@u zTvC&lMY0ZoTwV!v8+hN&4zeI$SXOqPG0dLkXg@Ly;cUriAvwcbzp8t9vpJP|_3^@Y zKYcqLavp=!;q4P0jx)|YmOM#we{LQf<-4X$@7@@39?Lv?jSqgdUYqPv!iRT3<=iI6 zMu2k2->~cTGnS2+>8nEIM*NDRnZ`>N)Yw%u551f;qTwtDe}w0+3?03NT4 zjbzNH6Hib17NhHIK!_T%lcv&?k*)bF_CFht)dpU|NQuTAa^bKc%6i3Esx@`}WNI+W zjW7Q==F69Bt=szsj~9vE_enDzEq2~#yxRw-wU7|`41C>w@b9d_&GMbz1-fXJY?Gd9 z7w|!|Q9ja+44YuG)@l@^k$yH}=g;Gm-q@8jVG2a<(c19C}Z#ly>g6$=%` zZ;*(?*+vt$D}z7qKd%3x1=mR}>eB;o?ZAOmk&WSnN#Mg6m^Yj-t$;pPk5xpdsS)ea z!$3I_v1Tab`;4tZsz`}!K2=xaGP}Blv*~MQDIjX0Tkh25XGpVCj_3-vTiu$lS^5++D#XDrNW&RA0$g1>UE0IB9 zP**df`+{5KMKJCf#(@~zUiH~azE<+*FCf^^H0VuD&~baRJy_;E^Vp_ux|HsDfLhVy zJz`RiFx%4k2HH0S|1alLnDVu}rLwr?7clF|QK9*W)Y0n_Q?DJ0GN?MZM`y0T`R_t? z(7f5>WSx=~gWn(9Ij0E3={jh5#~fPYTOL)o7V+fZFDgYDCUvm?-Ro1LMk8;#zVT9` z-m2U-c`mv`btbT&^X-7bj!pE=zJ{FZzUkz?lU~_Cka#|^FgTPp>Wk3jxxanyu(uL> zA+!T8c!{Q$;v54`UAx5ZtArC@iN6lnHJ;W#SZ<|qfxT6Zj&;gQZa!Bq5f*>@GUkSW zux9maZk49zWhQN0i!7X=Ke)Uzb4v0(2GRTEHuQ;`q!p(}2BMrqzIO>HwmAw>))Dqjb86$^+oX}GCLUNQ@;}JoOYV96V+YrEOE@o8*JLD z+8jGfI)V)l>po0g{sC{Pk1mfbum5=hyU~oiQ+CRDJ>vLanW35kbll@K2w1seQ)LdoIE9XItGPs|6g%8GlcX znWpO$-41T-nL0W6BAxu-MHYHd( zDhiW}%sp)~`*KY{(&%5Gk}OwT^l@02*+tenCQ8P69f zXa+p@!c+G;3NZ}sIS$%ft6r17l^n>X1Kn`O#PPi!niG6`p{y-;QG2;oC@^amiDCw( zAL}aX?*+{0s^b!S$wJFS@i>d3AYwzo5AsWnXcBeQ64T zhHX&VXpZV4G<{gofAA!O#?z4u%S)oX0ktLgv$hB?L~eSJrxkU9dt@z1ep+N~e0@-5 zC^W^sLTc|;BM93NV5r(m!x$RHIS9Id7F=!DHcldZVP2oBc`|j6N4iObJ#}jI;RvU9 zLo2ELl8VaDnFdU`K<*T(){NDQZ~V9dKeYH!ugI+JgT4ky{B~-1?#z>a=!KVUP0+%x zt94Unc6LpbkNA!=9}6^afPpxZOBX|b?B4s_R*@#u^N9^eos9o=61Bx@_w4ATv-DH8 zXG8ZP%i2_HbH%wfrmfwfl$cNT_55tQHV38daz1f4e*IXk*yJ-t>GdM!N_WXXj~S}& zNOT*u;ToTRG31TEIPeIm`Ho_KSBk9`$?{?dH=?vrbTO_W@?EW+dY9}{hkmivc#Bf+ zm4l~+q-C8mq7qz9^!OU#C9aB?OYFUQX#|{74Ba0)8iwR5U+W*mIuGe{Y$z1tW!_dT zj)kwN=-kR^4jQYqfxD1vsH6kFI}O_5+jwp`ti72qK_rb26P@cUnC{{-A%x+$9+3rD zUVd@cY0YstwHh2p

iGfZ^sWR8x8Jifyl)1bt2^^HFbMEw~u%LN64dYl2nIWXMjj z!Nb60MPLddUT8&Zz~g2DhZsyGa$Jr*J0b>QUPk**_ELXRCBTf}$G$&r8qZBrn?(?) zog>ysio>;CsT5`(wwwXS+G5(xL-m&ZlK+9(=R@Y zx-r|WtMx8|?re!ELjo14iGSXSWO-_JInzW8hiu*X_VhRXY(VzD7;_*55z451t7u@z zO&4{tb-mm$)$`6PKHPVHGe_n9R~7mQhi0HJ$nzjlk{lFUMSS+ZO)vziSBdL5!2Tf3 zu?vpg*;Sf6D^4MClr;sGj-|z6d8KNZuWzN;z7%=y4wZw061^u++HCD%+9TOTIDiOH zDcT_ahnw>!U z61f;F6hX;X%9}Be`ADUYu(RU6y4t!Ftx>Y720uN@g2*JXq;WD$-HYKSz^Q=BEku0x zRtYcD`1z4{ApV!zY1=!qL(Bz+=nux*XZAc!3rynbbz zQ49eMSS^QakAX#cL{t6P7uDQGy-@a@8B54=jo%~BpL2oq;(c&H!n}?#Bu;6P2%_D@ zwK*bOKm`t~9Xl5oNQvBtqtKuM6V;;X+S>iMllNb?xlK$#3A7&5wQD{UFDCCT_*?Js zJW7|Wd-eEZyR#TE|Lk;#V+<|pik&HUd3u>ON)qhTGL8MT<2y|AP;RJ@=DBcM(EOps z;ewU(_V9R`mlwNQNW5Ul(ZUIwzDE(m9iQ;n6>3YM9J;#MMwa*qARVbrgYS<&dVx6| z6Tz)1*enI8>YjwK_lKEKk59AJhadEtmjkA7zh131vIeYIV*mPFzucMvD|p}R$pQIc zl&F7q(o%5$%AiXI_V1OlP;WgMunWO7982eQg_Z&@yAh*Jej6>4XKsksFz&plr z0N_TaK~dvvB>%nEYKn&A)^6)#Z~SgnJw7*M3wPTrcYpOaqlLZkI;m9Kx06&2D%-7T z{xSf0>-4vLV43EfK{*r9k_3@ce_^mQQQxUmLX6gq2s=2+Iccu-+BF(C0LR*8RO_e> zE1T$5mi+m}WykI419Lafs&g8EAr1aIYdzmr)hfTJ6_p`a_{>lgZ^`m?yYrs>Cd;B6 zllMN zVS+}!MI|n9g3PH=EHsc!n^Fh=0D8tsaUb=XkcSt9MikY%EzQRAz2Ps7m3a&`*m;kB z07S@Vl(V5P(P6?5N4ZgV7XsOb6knC1Dd6He6C|?N+ejd7d^G7u?Q@xs9T3oKvpQX> zo9quBxS7aG=fXkTLvg&q1H`%tt~T>El!%>U>YWMP_x%qE=VYl?>E|{^q>C2y6<(NC zlsNH^;&gIShW$u=YwDlP2bdKRdYeb3P&3(^fD>?3^B zkKBo_HhpJlS%_*wJ{h>~w3wu3@0ZqMuH8<;%clJ(AMurZmcPDd)5*HW>O}!>vTlf3 zdVD8t*l+-#(R>D)cX#n7VfScC|Ka4FxIJ&*a0?FcUvG>0sGWHFLyZkrgfd3##8#@9 zM-QFHa$TUL+~yfYJX>LeO@MQ5)v-8U_&eIawrK#O45H7tHKA63vKj)X*j~$U!i<4$ zS}9hK?@zO8N}^vi$*%;?l4d-=$72b2SH&F|ej^79I~udfhX#IZv(TQgfXLo5cgXlL z8>jziS#}*Nfu<5Y4gZp^mWV#2N?1nPWS%a{jsYpx>x1w5s3#+wSmtJ#H3R!21YE(Z z4d`s)B<3agfg&|bB$C`l7PSwF>bhE~HVzq12+H`TspgM?>#b|t&UHJZe>g`M>)ptTWy(T@m#~5#`a<$JoKf*9 zIk#q-QR>L;zBl5S>s3$1+nU6Dl7X?~OX5cmZr~Nu{nLNILZ!gS>FBTY(T`a@zv#iFk0m6f$A77Xi zi$sd5SMOa`*I&0_?qzvh93wH6s^;TS${EC`YOA>NX_W4b9Jh&HNo)h-P|T80LYJRd zm#0|Q59pqQeLXToS%$I+5Hu9wshOlFdo?c1;pZO+s-^#x5$Vdht;zo}(#N|(PtvBt zY%Fr9are3_;Q|#lkDXxU*+Okxtww&#L#d=a?8yf1&pc;RNB88PwAf$I}RS z@4A?18O0IfPo%)Ub?c$~MZD~_E+&Q=87A|Wa%9U^SmUUhN@gRb%N-nxo+QNy<1LFN zL;`sR&;8U6CiXusna!0dT3Y^cfp$HCoZhs%J-2$i@Mvfzm4%wNc7O&~GJaP{M~Isu zgg%x+AYsRINa38dYUV*Q6s-FQe1feddHc4eWb8df47|%GfjIB=D(B*#bUBF9i(h&4 zUzKg1>EKoX=oOplJR;+8{SH*BCfN>fUozvgnRlAKmR8kvT7191?n+Jdz36mXW?ZjZ zMBNN72OANqB1qeO8Y~%>;_UEQyE9x~V*;#ntZKi^^fK$+Y?s6h%LWuh=I2-ipG`L2 zYb9T9-$(7{i~cpW(7#8Y5&s>#@fk>{yeOdCBac<6rxGIuJLk64KCwJO?je|p^ zF*%HJ(XP@D6G;OAZ+pKs{T&|rd8K6@& zNv#9bnY~oPgY~`~XHGP;By>TtNu9OVtsDw(5v>RyykV6om?or8$_okUL%3QNahpG(V(9y>#_E)s=H64*Lptp|jUyV5jEcQ(-#()jxC z;$8nK{homx6?a*K4pL0^OaJyYDgTv_mBK=${$0k`=DzR3Pu2!xb;`>0 zrk9PC9Q(GOWbp@DZnb3(0%-P|kLH3hJN<$J#&$7z>N5=}1-$EbTSntUu^}q!vC}cD zyl`8Iw=u&PpQ5QbJlwUx;y@?C_><=V8&hb8bvCm_ERoUCf*2XNLjrls~(<=0j z^>EkP)uvN4jZRbPBMZf6TsXVd5{AFw{^o*{4Q9D6a_r;6gKCfMpK!#MJ(O6zaTyqU z@+5jo=6j4eQ=>bAL+brWlju7%;dI*OI!~yX7?*aOE-g5!-Xns>+xW{>>t@c2#RZT2 zC52DiQEOWP)`c@?!@JyFV`kDU>)Pg(x2Lz`at z?$?wu3~lNDqNL~Cj0&NTcOZL^ff=GU!%U0Z7%C(Heh zn(*MC)8RwPfHq@&Bd#_~+7AM~X!1u&GdH*o$ATFreJ>DQn-YIFhQnrD(O1Px58L6@ z-}DyB7uXtA*y@6PpHEP~SukYXnd~&FYbs}W<6%3-os0hKCyVzb07at~0+weqrq$L? z%d~}Bh32$7ibh(`x+^R;CZ#4_d85HMpl~XAH`@1>2AR$58yZQVYlHnurp zN6}m-)aMSN#7FeRDEXOb)-UeZ2%DIVsxd=w+GJ`=N?nW1_2RW(?oZ;7h z-rRMO=`O(1y&&+<0rlqvp&%!F*Hj0nfyf4ps#T!20}oY>W;@7tbN^`jrt@NXa$NQu z7Q;vHxbIli6w=(>IRS!yeT=jW!0#mjyjMK5`7;{Qos{2^xZNChT57i!KAU=^D20-( zn7>{1fR53KCPkq9L!->z>6x!3mw=bALL_Ku(i>at*;t#T7=-<@%;!>pu;i#t`Koh; zIy+z3(M}dua2;|~cGCN825+F{c4ZOzulKKR)fuzLaOnxY|3}+fMnxIDeWO??A{YoL zEiEl6-Cat<&>=a1#2}4QN{4iJ4mk`CH6kM2Jwqrk3^gDveQx+a&w1Xp-uJ^<>zs3c znlCeZ_P(#)*Y*3IOpWwV+LV)6J6Iog=J59(w!ZRS_tnNoxzF}H-qggpKm})Ldb3@* zZsNsVvti=r4y@(RSJA!4s9KB-88CbNCO_(~5P2PCy10I4yL~dG@`EsApb1Gr3P2Yf z>Hd}~f>E7s?X$Z{riZggMYZqn9k2#eR|0jkyjd3Dy_6~DJw-`~0a(A&g0w1J#YU-> zm_5JZrlu+i95;5s^6|tze3_3e_OdYIdn(gW%i`WA9`@)u`8Z$FM3#)xk||FW`JG`I zTI3W%Go{s&2TYx!A^8nJXf|e67V#S4j;<!EUqY^R9=1X(C7CqlC7ExVVx6#fy~c)GxOt1FcJ1j`^CKPkKB@s#t-b7_Xc9 z3Rr^ojfuXk79c<2MNap~XMHRxr1OXmcSA)fY?A7!h=T5CJyN(^IC7r`NwT?y$D0`0 z{6is2V)J$aN~}q^^iN^;r2nfKPr7$2m(>gK^v89QXM;}=z|=M~t9mgaV1E8lVsqOY zhI-~x%-x5uUTsI&=LZ%uIdkd+EUceYvD4#n8DgnojjO!noJ4X8@-uW=@Km+G7yb+SEVej-I0Z*jgiJeh(VAMXmzd^mdW zQiBA)!HXFK?OMs`RmYKQRd7Vwd?4=@Nk1f{l&M>V-dOvrP?U+sTarKcYLK1HZGJno z1)i2 zJ>JCK#n_sa3R^#KN>(atp4og<;isjWVBTKYA9LdmoGV!Vmb```5%vKWV}mAv^D*b zF3l|$X4Yc_T634#sbao-?moYmuyrB3mpN86OE6ZVW7CF56bR$i?)g!swHksQY9r(8 ztIU!xF%kklSvKVw>}(%JmYf{f&UZn&_nU&wuqNue!9(MzTX#+h+KUdGn|HXderu~L zlm|n}koUEJ34N{II)^XX+yPt8!*gZG@OjF7zlUOl6x3;>I|`)Dq)dU*G0SAga%~{Q z;c*^;b-7B5*4xcQdY0cK@fDt5C*Sbf|7cGh4B>BNQX?_QGPtWX9Qh82DOW;L&_|uT z1`S<07Hlq4Yy-QZKVFTEgMv%jGDHGGD&9RFoeQfMmM=!aC zM6m5K8yD`2=%LNQ#U*ivW0I&J+q?bf^ymDNeM5Lt9#*AV;b5uJ_RRHSqKb6~ zm@Sa-9*g&O`$Z7Co89=d*PW8yZE)(acc=cu<$gth^TJt%p7#gOH!_c3y(p>rwc!x) z`dQH(32z_&5(2sm6qHIFNu{yR%RnnuY`)T&M zI(89ohlcQSS(V7MmB{Dk&9BWLLwNmnz}b6^Z;Qfx$l5gV{3HnjZ^V@{Rws4`55kh?gsEE(55 zUT$|$qp6WJ@#mz{?B%9$Oi4Dc!y@G9UnVilTO2$9DwlXtm;as;;z>y^UQ`9LhPctEEIed!gLQapY7nVs9cO1%+$flD5}gQe`en(K6O)yN}Mh?%S>l3j?) zH54t9754j;{@C~%*v9O-H;VTSdLGvkciS$Wx!GsOkp9PPesQYL0{>po!?>$jHkbl2#TZ}ZdFu6EbZ z)B$TqWWJmJUtV2*wy6I8eO-1s!n<=t`p!Ozeam%7Sog-P*x;Q8#g*}6OlI{KpL&hJ zW*Nu8V0S~47ljf|{8^%*_TC-DTRw1l**_zwe;j_x^BmpxEC$A|a@hn9m99S-{@CgN z_8R9$s+I3YAWuF1p0@0+$9(qiQeF=URxaZ_>RVQ+er{qgq03VA?YkpfF~@grdP(nzz9tS9l)R@7t%QW4NIE|2!%4-+eDs z`yj4tSkKAT{$@5xVpeH2QCn7=++F(affhi9sfN@USt~An{@VWapMn{htB~PD{wmRt z^gBMDqkh%s-tdXBGKhe`Kb+HOnWEm!uQ$4ZXDkJJTQcK$AB(pZxPQf%0lp{I=KOxpwux<>!G4}8LVeiqm&2Czk zq+jps&3MXKP{bx&N8i65pqADLp49;C#nvYld37Q%AzY4M7~% zpJ=HA9P~3{U|EGOndpkm%2mIZi(I}E!=HM4(+>6K{Bh$_J!rvwpIrJ|P$@P}*Pd_j z64JXN!P1n!2D1MoH5LCv=i(1&U@SSRrzS9&dGV>QK^2SwKR2W1+v=li^!>Yu2O5n> z=;{TYt?eJ0h=;jy>)j+sWe;?qtu`StHDh7aH$z+6dXS@!YF%fQC90gM?6%-S-^I81 zW-qHF?EtgnaSgH5R13x#y(}w@Bh+L|ro3o;LGkr!=n2i7<2Ejh*%32bX<{N2xAA4^$B+$%ySv7MA?bqc=#s8k zYNA*PgDJL$CPvOT2^zDQ4q?Cco@1Tk4Yl3Xru#ohN{r#BRDqMi z11oh?AJ4$vMb{Afx)E7}#*cWYBWPm`(?n(uJ5{t*4Ji6gGcI)C_ryn$KV(|V@YfGp z$7Wr^qsE*>B^$RF28YRR;H_S>;mwt@zKoaBPPE3ZxsJuxUEV=sNKUQM$2&d@eH#h3 z0m3TpF3l^VX#OM(WdH=E-I|(x$wR4Q7m0qn7KQpM_h$vw_R~%4^m6S}OLd(ZikB)Q zIV(|5+HNMI9^G5HV2HQW$XM&0wF;tQ{Ct971?RQ;r>AYae@Y_g`E`}@Jdrd zm}4k=s>Zv1+4{gIL5(q&Llh)a{343&sAow(x$mXEeqZHF!z?`^VseV0>RGC|g-kPg zK4z@kEk@f3CdL6Nc;^J@Q{Cp)d*0&Br90LhNYiukXyaq4Xk!}ik>U=kNtK-*taDvt&;k6;2yh@u4{iXUgmd>oWwgu;U)qcn_so;jVTMwY zdkuxAx*w1{b9kNl4?@3N=3j5K!9ao`FAhFm&1as`B~K<_04M9uyCgFQdYFFYF!Pu$ zui9OI0ws2nCqLskpP3ArCJpnn*fOVUN(~=uIJ(r}{EFy$s>HgxgJ%TYdf%uH;~jpL zq{pPr>fQt1Q$T4zCe|lHVwIl?S|}X!Ch@vmo?{4e`V&==lWbmMV~mN4$0x0>3L>Kx zFtk_r^3ULU%%{XiC(7Bw_PdJ=f0dg)Iz^t)6gBSr{7S!$tj^S?2lZe0W6sHXhThNQ z&7a734J>d`)~Gm}%!oto2dzxj_=~lIrD1?kdvaa95fY<`#d&N4z36OeC8w&2PkS1Z zVZE1J4oN!(HLp;x_KKC{o(%T$Iv<2jw^E{D>=T<8 zs|-E7)$wp?@^($FD~&jIjocM!vxOnUJ6oYXsKX5@oT@lM|#HN81x6>j>_4=dC+dx@?9VS<|#RIAo%?t+Zu*VmnrO7R<% zXo&dvr%QQnbbW3;Oz5hvj4N9}(cclCw-6ZBMjxVZ0fv_)wGh&GMBj^wLIq!c#xLj8 zpuxm@du=8-4C(zlKG$ltiF_JM&A@FeYE+zAFTbnKJFT1Yjc$sQ`>aGhz}09v?DH35 z+VL0DI}=gRrc#HSdm8I9LN3sKRasSoh+X_DlFHaX!y+>pM4|CvmYvMlXVrBzyu50{ zEZnktqpmJO{riZe%tB`VP4B(Qw{=qp5axTUJL7PV44qLXdO~H)g}1+9Bx9stL+0ksg@Wr{(_E=A!LUZ79)M&F~EVv)#8F>Z$D9!gu!1jY7fmZdJ9+GF#FQPXY_G=J+^!Ke$H)ty8tN@ zk9SLhxtYFBReqRKKXFC4e@(w$DDS4v-)TX+n=!=$8BFXhw9mzVund`)aIqMZn%rZ2 z)cELgSh($+LDFCT8%+r=Jj=WvHTO@hYTQeNM7w9A15ecG&L)SAOCCLwq^J092jY0? z+A~Y(h2r6l?@_8(`#b)mrod2im`=B1ajUJ=;GM?AvBH2q)D&F8mP&y2I->-8pJ!jFDi zB*(jBV<1d2OS*OMY7F7W59(yQgXxo|MC{Q--uzMvZ%Gn3b=yOnDYJ~|DA+oy9L}~V zkoyXL!-a(YP8GyG`ow+7E;Ra;*zOf3RmQFjroYc+TJ0m#K3)Ie_l39da>+CQgrk!= z#{zr4gs=1+Y4)TLp@W0#?)B%o+v|efm>Tn-F51RIV%v+E$+DiyoHRK;(z5wP3l2`n zGGzJ9&Tn?7c8l8=VN?iOc zxDTQzyYZoF$QG;CIjjQXe0LuOly3bMhXAA`(^26U^)04lMy;r`4u+{r5Z72&#-N@1 zJKfvsakEEL4ZV&+I!YQbis>60omM{-sUtN0y@v7sb*Ljb7=FYx>Z1p8MK=Hn~A>L2bTrIl|Vp^_%RhH;ChP=AWb=!HlgDa zEP^@$)OP>Ad1RRZy~=9EF+;T&*MSv8UAK&b>k4Buzo5!_l|!>8*__Uc3c-`kjh?7j zucqNHUq=uoX+u#(x=ryD^ag>hetoZNL>}kpl|Q@YSNH$@pp$rNN>v*SwZ?wHEFft( z*#-Y~V%4`hD!4?n5qzu*qQU@maEp`&b2q1IGS8++r`_K?!jHEhn{lpmbflP>-5&xB z7=Mzl|HRhB@g}+U->#)B1R`~aQ!Q`0j^S@iLK*Nh-(K0%VGItw9;MtjKJ?~u0yt7zuFHTJ(PloX zTkm)%&u$fS>XF@l>Cje*PR@=TUDdkpNhIyPb}|AX*j$gM>?D1lb)Nde1Gx&r14;h- z&K0$DKBj_kzx&TxAIyp)yE)Q@9;ZkiVRA<7`7&(om6VQ~AhriPk+DqMv zcOr=zhjHHCO<`Avm?Djrdfocz3G_7@&c}M$()L5_VamAk9NXg282;EjAZEt%2rB@{ zLC%*sR0^`T9LClJZMzeZ&p6f9Unr+=HP)~8FK}_E^jOs~QANmwC-V&%8o83)BoGo* znH$VnJoH(P1}XG34Sa+d-eYW9Z!#mBGk)RiO+&yhpjuiS7Ai6N;6IC8m&ty6G22#v?@b4|1>9f$Z&P3Ei%&PWZ5_#@C@wi1mDM=Uaj)8Kpz0&!+S3>7o8!f%=k3E zmK4Y~$r-GBn#OS+8wKT#5BgY`n=vU1tU{N&-GdJ$zXH*`9)!O2wR^%kTWgb-q&S1V z^Vz$+O4QdTLVz%{&EEFSpaipGTY$$$ahHwJB98s|m8)2tj!Y3CLcxz4TbpliE4omP zk>Js+`nT<8*eswcI>%-2Ox(w(9}C~hyc!J_E3StQeZ=Dxp9z)f9|FXF6}gDidKuizCOko?qm=y4sY~d;U9k=0Wy&Wm(QCYb{%o z^7d+7hqXDe$x5&3^PU<$LvxAL&vOr*wW$5lwN`%pEE#{#APvjG2OOWz%^UWm<#m4| zOzv4bu5;YZ2mhU$;M_$;Gcd7qaegdjelH~WB*^yJl@)wL^%gJ~H%rE-Uu8{)tavl^&*TJPP2eX*XiIv~yX_~gy z%FLTgSl&v>d)mBy(njL0{+b(jIqg^b2r>mg$doNb=ZZ--7TV?@O^QjjJ!m8}j;R7v zSp3RKW0|=$lj^eUpg%O6_op5=4W(3a)lPG1g3+%b>K&7(bs?Ex@3?b967pa&^8Mk!`CKElS>MkEf>zNG`hxC zbr^u!)>-%3e5CL0?hzmuM)8iH$RF10Sz#Y+rA9gHSgGjT>{5? z)D~ah(du0t^8?K*7%|2){5XijE9h(ftSuZ7QQc5~8cz1f-AIt=>qq&2xE1ZM0PLW0+AMy1uVo!@vnRiO zp`N?dI&ezR>t~6C03&#-L1^JCJAf*85%2>msoAVpvhVMz2ss`aO7&$!A}pvdnkFBnrZhb ze`$bVO_`mzy7?SZ=3-|jsd+1lP#tu(WoX-Dp5Cw!zV40HO`8hE4P-`WyxQ#eNYT~uo!)W-Q<)-tcSYd@Y9~$2F=+XO7N3Q0| zbH6#;B<9&K4{4Xs>Q29{ZrRt1gKbe{9%!HWGw(g?G6E3+)^r3g`2)KH7Cn5G2}y^V zkHL#9PQ96(B1qB2mgoXmG-AFatQ_@jNT@N^Wo&S1+ksNaq_(3fhkrYiSX^^~a_Pv% z2{a*ChGTCN|Jj%}eZ*EG?wB$0oewVVC>o7~!o6EfH^B{a?EX{~$qpG&NT`IM|D~rh zArLiS9h{t^?gd%c|B8C9WoY;kK=MjaPGlws%c)G`EBZ6<%hWGse4Z~`^YG*cxf9EA zE4i7Iq?zcb2*2pz8Rd4%n4bKv(Y_qsKhYOr2bu0p2kIQaNp_jYb|Mx|v&E}RbEVcP zC^o@Wt!HlNcRV8%~tI`mM36F4ro<)=J4;p&{FwFucWE&h`SSEu~tf*Q{g_wodlvH3y%9ys6Ov)O@I!hMdN$lW(JVt8|ANJ*VnUS2C8O#AP9@dRJDkT^l~ zOD{H87)t9FHSUpPxM5>;d!gROw0Ui5xAS%sJc6NhGY0;riGps;)5CW=BiB9USgJa9 zpv+m;L3rf$3zZ{W{JX$xu#5uOY^OZ}P9U0+H503m;r?U28I3Bj3Kkx+32-P%-SWje zNSjzUEMZduR*c!cVsU9Txv7XuDkV&F9-ULRVZg|7`JcV5*+i8ybU@Zg&{XNMu~W z$UnIvpZktCGH8!DRxs7{^P__oOlnkcvt0eROjR*{8ysAk|k{UUr? zg{CMaCt$q{Crj8sQkj{nTwRhR?IVtx-58b@y$uI{Ys?O`NBc{TL(-gG;NDIu{Ld^Zn?DoQkvp(`BJpqNTZ93n^)J}f`L?b3 z5fmKpA))1Ge^QT>uopOwJ1Yyd=!vdsbln7Yr8U#rr6ws6~;f zZeee`D#|Jnx9q}C8OP!$9kMo1VT`8c7toJU+UkYU11uuX-_0cwth{$rG?6X#22Z4- z-Xe=RIpo(z&I?$GWML<&So3i<1u$h3MFWXOxg074UNU&t05>DLA@E_O#_lcJb!)4f zHtVny#}<$=h|EGL35fX@b5_P;bc`L$%Sqjvz75)dC+sP#N4dtr`J#*#Zh0a%Qut0n zqe!&SDz1B&aJTuu#hE^nk&~H0vhXj}pQBxT2Txt}abQ{WxmPEC3NzJIS7it6z-5tf z&#TC6^nWVLx8y|gL@KLUj1Vh2e819X1iK9N`RRNHJW%8f(BblragfOd2U|bTT?5g* z#*8>-)Au(!bQ>7_i`UE-{1^P;_q9aVsvC&}tiL%rjL^NgMPhreEgRO`mnN%&?*0j9 z6m)48XBzD}e zeK@?<7afKY*)wp!!#pUONR&AI@@m3yke;4G&A!iTS4C-qjif4SLk@xFkDi^y~gv`V5jXTYNA`2#|N-_XhrKUVs=G`;ULYU ziot>su=G&_oPpX6nzwZ06lYs~#7@GzaZ^CV!;sy`3LP?Nb~Fb+mhsKUZ7@P*)XaCD zD?0g(M;H;sWp;fF1YwFLApRUExTl5|Rs==tndE4abh-=&)S#~L+c)vQYgIQ%$_i@0 z2X0vuQdq``^U7_j!2pk&Hbe*kgKR&926I9du$zuU^6tlG$))-rupgoe>&;Q zG89p7l&INeSyYuNb~wy&Hz6(AYdjoyBgvi z96uI0}De z&pJ1T$h?@DndKOh+#z`H+xkTC^1|-eZRgHSt+)*KKduJGTq)u=ZbidC>&RvR#md&} zy0Rs#z7l3d&5yKAN>MeG4~^o-%Kvz54Az_ew1px6#HjhR#BCV z>Q(6(>akytpawI3`tQB+T_-SdmZFpD0Z1LSEH;}vcSI0rPSp-clov+q$W!0?DAvR7 z@mh(Id}{Z2())^qh2J@CLY=*LT7%AZXwl0R;n?n>LDu9lfQ6&9QtijbASY0(ksC^6 z3fKuLie8Hd4D6x*I?$qv#2HKp2=)DY&R19orLgUQ>m5%pH0=InSLdyoWglaf6P*Mz z;2+Hc9Z~mGUo6Xrmu*Hz2f|0%}(~e z&)%B(xCjBb$MjhgLA$?}*54pgtyW#O6nz#JhxAUFh5t?kfNF@Q7GSD=7 z%|@=g;$eNTEJVE-FYb}Y%R6mZf{3^N0*Pc&Ec0i}e) zn1(eSp)?KLH=s^YL~Xcty7(}Cx#Vc7cQGko#;a1FI5iV2I+@pYSh=*S=fO06&U{ih zLL09gh)_MGF=0Tag6LTSn1=>Sies%ZATW>-indyhZjB%<^uMIQ{KHkz!5mLstwdK7 zZu!eYXG8WyTA_PtTA-rZ-NVjC%H`g>8`jD;Zw{%kNzaRKRT8p1(CHE}opv&A?mLa? ztIOJ|cdkkvu@TstQbo!E7EE`@SHnd~4f^+)lV`?Xq z4W!v*cjPeWsde-6XfQzl(?yx?;2LSMk2~jy)pPfP0`m% zcUUa{^V#pOeu%$cpY#kJO!q-DVjy!X16rv$nu?<8|Fxd{?YOj$R|(v+Pegc9w4rL@ zsHGhxM>;w~8yntosMXx~)31_kZ@7idpr%GQxzb+uvdepK!GVzntgj^XMJb=jR`bd`TSS3mhqeKmJeP={ntCitG$w*)mtU*I4-W_ zS}lP(W? zip%5X0_rAj6>}ETq8P7i)1Yg})ndlQUFb*mXBKD!3#3iGCFv3se3=IB`}jP4Aa8A5 zt0;&gJ04hSIBap%h@ta*pa&X1EpxozH<%uGww|eVeB|=0Hv>t{;MLDgo`~okMAZPj z0ETvvYr%p5CcVg2)lZ66CyLf3P~uFbU{hd{J@07_Fe&FDkJ@2q-JOyjfY) zI7d?a85an%aYE=zG`X~7o`&9>v5nF%j@s?jx5f61Hh1;Dt*^6D^?Z|%dNfQ1T%G{r zKmR$fhboFuHCgMIl|-MXGJeuHM;Np+u^tq6kUp0&{+XG(F zIm3HU>z6sK^Vj5Os@gye4~tl?EgAOV zcN&8Uf9cdhR6uHAnDUiJ%(}8vxhX1&is9$27t$3`tQ0iiqX{=tT59RdeGdv))SncF z0k!w^7v5r3&ibVAB)gTZdjFn>%l46F(Spi=f6wLn&%Tu9LveOoMq4Sfn}6QkD68mc zt7oQze*C;lN;)Gv=`nKW0raCo*>fW5Zh}htj>v0$UV=2$LWUzq}ur}g|Gl)HhA-{!m?-VsjIKj!` z=w~)n8~>_Y~WVsaczG8?ST{b~1|D6!^ssP?~kVYwo&qYNnE40JZrWTg0Nrg5IBe z$jSU$X6&xv9NyQkS9PNVKc2gF0T`SyV#kEifW(E1XBp%XTx$a-4^U}lupitD|19Mc zq9|Fkv>Bd>oZCkxi3aaR)~hU^mXOU@l#bAq1d@VAGQ5yPP*>5@X}{-=s0IDN2H8WJ zjik1p;((aQ{YOkluYwYvyxhwE!zj;>p`(7WO8F+b?hVQ2a>VKBOo9I_;?L7?bM;nY z&LN67SA29EEM~s(iS+w;$r=FnJfqW3lH=A=H!yb)&d0f<+zgRoi-Od4=O&^XOvNOX z6N!i!iZ>1ih1qqCR8J7=A1MGjFhj`DYs5~qfZJtcG;i@ZL+Ei;$cjwg0HhBov5!6; z*i}_68u6$RShg4l#yggrCp0GdWTrP&H&LvrABGgIO#bxAw zzP}Fn#qbgf4Z*e8Iqcm%1B$NL&mBo)Ace$U>1f&t4qiYx#U8{wB0vW%J8CzIj zGn7c7?uB;LH&ECvDpjjdZYL&6CbDR9IP+p7{Wc=+toX&wLZlIA>zi7_r^Rf?jfhKv zm6mK7C4V)_iXU}zV!vK7xLJGAnbT0xGU0>F(D@9HK6keReSQmbvT@O`wQ&RUl8Q7R z%HFE%7ERejmWBSuKCn=c0&!Q6UhTM$P~W}1({**|=(=WQU6;+xj;630enI(7OWtZO zM*u~X^3sT%8@b?wbsDFueZ9RTOhYa5w1(y5o1a|SJpP}ymrlQ~(hGvT?qUjX(%yeD z_%DI>Nr$dLE^yL0OG7vI9cJ3%x2Cb~3wM{B&u*lZ+n z%v}!}I7K|{Dn#!@)WLc=n-bnH&^!RY@*HL^xXL#?qIChEgs_Pz__mV9|P_a~wu zRr6F|$;qOUn=j1}L=-~^go?!8IdBVF_fAyD2lcvI?b)Ar`n_iSFCs?O+kHRfX140t zMH&sIV^o=SKNRsyaUu-4j$I4PG>(rUX@|Ih-HWE6)(expUK?8z%f5GxZ%%&glAZcW zdn3&4_KP@wsaSXW*M)ZYM|!!z%U-Qn)qdEQIOR;i zZ~zH-6`xtT`r;eX!pg-iDSm3BZcov9o+eH=qrkCYowZ$#Jc_mds2eHqU^<#K=Glv9 z6#hUnO&C1Y$S(0J4sW{ltDo3_08ut$!I-or&(f0qP>|w6oq8#WIxve-$gf%LzT0da z1aSUfB9y$1$Eq14fqfy@8|{Us$wZ>Ebq=b-#5--vj-7|^{czoW1ZkoZ$1h1tiO|M@ zJw8a2rz{J%iQC_F51xm$Pc*_)E$kv+h6=vCUc8;^>e?~47Gc;cMl(}0$1UpE0gtE= zaw3O;<;UHTZ_G8DQ0u$Bxo0OkMux4zbdOWIW9D9r*@Ho@zb5pgXW;jui7O_(DFx)5 z>-gj2Pd!F9fRsNs7^QxXySj44i@ z8Z=eO6Qd=E+PFIJ-1*c#wg69629E!u;PI<#XL&uJdBd}qp={o0#(|t-mD9xh&)uV~ zoAJ3#QQHL2! z3CS;i3>@#Xip;>IyQ|nJsw=tGKY93o|F-{9huz)g9>(0xPwnIX=fBNVpfHo;!r_vf zaQ0Jlnu1b<9h8z9vj&Zw>~YC1O}cf|o4`$z>3FkC?0AR*`A_ySC7VO;Ywxd9*&+q( zwl1@ezOqzPFr7wiT<}PMKWUb$eK?*)tGj(K$V+TS88;Oh%%##W(4A>wCRRc|vQ~2b<~<)93nI0#lx< zhVhCK{os0kF3_K6_ac_xHQUU=bEDV}luWb@&OUyIM$DSfCWD)0>xx9ID_A{Qm*=hg zb`vjWI2)oZ#^NE$U)HF$t+r&t2^@+>u@8G2+6zn4rb!*(8gBR^nHUt5CI+)` z7B>>AtHC4jiI@aZ%Cp^`Nci*{rYB@TFwhf7oxrZ|*E)?aO>x5Lo07eQxGC(a0{0hN z8Isd;y5GH>Q{DLSo8H~}D-~&HN2vHhu^K62e)?yaLfFtWH4_RWcz=Q=#?-@m}#wwlBLS)?+{XXIStH zTMZ?v2!e+tsX@D9%YX7lskeIf!)S z%!-08u#0Z!T3Si3f+)5$1_9o{AmM8)UTMa6K9A$W;_WR5nytua#j0#Vo*@;94?Trr=MSR7@o zcx4wvy4Zm7_Fi{pN?a|Yo^nz&VP%G-1iHya1`3Gzw>2SCknxBM>A6$e30|e zE-2{WxNP`A+uBM%$MYHpvX<<9#}X?5P#gqF*U54&p%7Z7Iz(S#`<5m-sRhNnm{?%p zrqCVSHv0`4n@oHGDh*4{Gr^e|EeYyM(Ez z-zzMB@kM-9y0E$f*}swZ#laqZ=AzVg0qc~r!=zRHx@g8y{uIAmpx{|r9=30k)@-53 zlZPHA3nv4+XdXe07Sd*gQW+-C;<-Vhk8JiSW>153Hfhx#?FujYU0WSwPRPP$-7snhL zP-ZY_foL7fMtqdgh&^9Z`5V(Tvj5TWtOU{*juX13zjED)v!o8M9)W9Zn-WfdWC8K_ zXMRXnIx)Y=Us?2`nW@Xy`9j10ZK+?SUr2C6o$N-1tjMF6COlN}oXs+aM;UF}%q%qN zEI{tN2vH;#eAW*v%28+K1)u_I>-rmTNkE6Q&Av*f#2H=GdC+-WmODUO5XI! za`v@o&Qylu&4i^5|GU1ub~%{=a~cG<86d>9k{7NaXDgrFW?)f>81c)^1IEz=S z$>b^Jlv|RJ^7d%(2VMTNjg+Jh{O23&x0?^G8!~XIz96=XlnoY~5xDZ6)I@XEY(P2p zMq?Mln&JNg97<21iPh2w=j#h%A}b(myDM<#2L#uPuOQd@vdTjlhl zIIpQ=n;9QuC5q}RctRS)WH|LCeuqIU0q3cViOJ=Mg@EtQR@p2)IZfqP@@OtVYF7RI zd>HFqZ_Hah&h8t#*K>o!-)(rDW2WmYJB1q8x`A^6y0(BK@2`P5R(XW;%WhBQZx1I@ zo8(jaIF;4$%?}zmMQ*+@8JS{1N>m${ugeJPyyW)i+dOyQq^Er0j_699JumCH2)y|1 zjMxrUc*%DJ1-lgx^!Ts1*qnTD$&vYhTKrw04vD?830?mY;n&ZeHcm0r+kU_l6-mGB zUNceDYF?taw_6Oz#W=I0=LSqDiy(8nF{HX9Uk(xYeS@~JoaNVA4|bx>y_3`4sufbIhg zEIrd*tbOs$%q=(wF;a54qhcC^Zqrbzx`4}v!*k+0osZ9?g{cRpm~->`%+>YDM}(EE zieIF@=Jo0FC-C`S-{94_@Nl$eUvkg zHb?km3mkBH223=4DKm6i4G=!WhCDWX$?2NbX9D?ir>LVYL;cOaHDCnB!~Uq%75a44 zp&L5!;^m;1vbxnz;?{sblZqC^Xr_cyQc^~6g4In`oWX`C1HHcyViNnlXS4yj`!`k* z&FN%RuWDz?fn=&J2O(lt^x=Jo`Ifg)W~##WW}S;AY4g!fsmn=oeGAx(g(Vr9JHL# zr&>R|`#thtlp+ddw&lQ1G%7VFyvF0CkB0BzT4J^|wi_j3^+xa^T5#Se(lN2D2_3Y&_Uf0ftbPU zJ&?PyX^xdw)mkCo28rNk7EbcgIo4R9QB9(uQQv{$v{uLSA*{OGUDTm7|FFJ+!OT)* zO2n)n+k+Ld@6{N^>V}?x3i#=0)TC8zIXlvEFnQBIH0B+h{h|m7+>f_L%t1a2CpfaA zEOk6`FUISIdRErs`ZG5ZcQ7>1K`Zg+EGKIBNaJ6+3#kAR9)3M3;7@75bqrUg$>h2` zde;b3kk!pLmi9CSM1!o8F*1_nojv*5q_P=?yWx#Fx!=49K3ExeVs9V+hSfUw9^=9r zn0$&dEby#(Zc#ArgM~)d#OQ&3R=vj$gPeXuo62mk&Z;~}jT+X&dbP^jk{vK*@esCG zn&s5ErUoCM_`==C=`t^(qNj7BuITlFN~K0z)QqdaiuUhf^ZQfX=>!U25qnCyv%es_)6Y=A^uxs*eXIH@@9r_ z^#o1U%KUH!Au1v#7|$kP@};#s70t}kSv``3&C2=n|D{Hz`I_x& zghUfmG*p$uF8?qu=TxrkY=7U+uwKACe8Gm^M8^W;snq6X0QcMbX6YWID9CO21Zt4) z{(sT-)=^PL?c3-eN+<&=0+K@`or2T=A`()P(jp)Y(%lFuF@%(K3rI?*h%^kHLrCWU zGQdy+=NWz9-}%;e*7^Q9>$sN7wZP}udq4Z$ckS!GZb@^%<$8Qqzq(o5eS(GVl({kN zx!TlsL3E1vxON=GXxv=aO6|e_o&F4G0vboRWTh)1U3F^Qs(8g)w1_j;6XwCDy}t;m zIw3|{OC(ao6{w~OZ5^euq3)d9>{;dZFVS;(w_!Ar0jW2YH%B$4!EC!yl|S`aGUXs7*fqPf)y!1$c7KHn^r{Aosx3d9KShy>^RV3SbZh%nwc! zSjk$fCPpWXaDcKarvew+*jc1F^Z2+qh4yRX@8iQ$wLra+selyKU{4we&YDOUT%Q zYm=e>CiE}+%{y}jua2LYestVaoL>R-RR(H0eA1h=iId})+SOl;rt=gZz`y964LNe= z(JwN!>$HUymuwjpCb!ngNs6%bvyeXbTr0EO&TnP6EBUe9KQ46X)WjrUag>3h+RSfZ zd^Usoc$wsR4r054!6oyp8IZxG)Z4A}h_@P+HBjdLU-QQ#)$v6UJU!90_W@+;r$alh zr7yA018dq?iz^<Rg=eF^n2Y``}-BTH&WSDnIvYR3M zEGU)4X-RAFb)%=&qCcmDLr;sr;=dP|0GwY50JF-7r;AlP?%+eF^oxA=K$*M6 z;QaX

xkv{SJY~U)&!4j-h_g3EL^9#O{uq+tz{qz4`fz(mz)e&zvbB)&Fi9vK8OB z#;Y6opq!I2=$*HlQca^J0-kk=I=_vQFw|&rEBUbKxbZl*kxla7Wfcg(-&X_LFEdS! z;%8KpMx9GxeG8q}O5)2Z2N&t)jH4&x=fJrSNdMnzEab?rH5Nr-fCmA3fQ@@`r2#C| zqFzO@(j|AP20+vk!1a)Qzudkif;>;a^5y#r;AUzo-bId)r>Oj!`OEi0a{swm0{y+k z{m^7Ta?9wRwk6TUQKVtRpVaqNHk#W!jaePHMUX^&Gu^8_Q!t*24SS=;&%oF;{~7zo zL02p(K!svXVZ-(Eq#vi&y^v8iQL$Z{_V7et&MrQ^%DirF7VQkMO2u$yo6n_T6H_zKE&lNptC9 zVY|C9_yjqz=6pQEe=mr`*rGE4h!mgY$SpN`da$;3Et4&U(exLw2%gQH+xuk>u~QB- z?4qI$Kz+)`rpj0{Bf*&1pD#ge(9aLp^LQ{@5QR^#&g03coXVW*qX9g z-iX(F-zNFl+mJ~f@a*b)v9>>dowRCtX#_{TIZ!%4#5wTpe=cexbOBKqwJOx!kU;#u z*8ph#pKAbU?aYP$ivm~*!J2>OG2+^(qntV(6CN{VHlOlJx(4LWSp6eXqqB*}1hT|# z6FM$Z=dW@+HmV||Hzn+}&_s~`nG-HnuoMf@b&gy1(8 zQHY~&$4>u);h)=lb9ca2*zUiQO+Iml*akZofPbpHSS zhysumK#2eT`TrA}|Nj;KLSM>d(4gT^zd}4yZxO-V&jgclP;)yz1}=WiyE#E1`mJi* zrlSrL@ibk-SC8%s9l(Kx010 zIkrqJEzSzpM0M6N6k<9@PYR}x%DPj$>>N3M1vrI9uE+jD!;RjVyWhBtoscz$=c$gf z!tU2V->!fy7t8{gZqHIU4PzbetJer*b3bog(!A-F-#LKQ2S^e2v9ZO&n?tQfaRW`e zRg&;2zV)AuzOt=8lY000uQLQ4GsZ=Pdg8IjyTX7yIf|K2kd`?@`hB2;p<(X?fkeJZ z4>*|-ShRy>;m^<1dwoxPGyB@I8f@gUm5;oJUR-0Oisq`VVLR3aX{a^cel?!vijFf~ zLyvUuM=_To>;6Ae^4r7GaBB*q76`byY=Ld!W9D@;7%v_D9YET5T}haMLS*a_E8UTm zZMFwR`~Bx=r(cCq+`!}m{!adx+^t&zwq21Y0k%(EW@@JTuEq)x#8-A>BYtq#pP86$ zsguov9d&6igzZa3cf)R%_%S`4DEo|O8P=6fNzGNSv=^7j?n~G$ zIMSe#-T9I)dRx0|YUKQe#!e0+z0@RrNJPIFNa9fHaFS#ht1$}A24;@_H*+>RAG>hr zgB)N}Xffdsp8@v+-qS7-J5bSebh)wbc_#&O=#0Y1Jiw-Vju29#32iGi+cPXkcm{V% z&=In(D02N!>wd5j?qqg4T6^#-^J@IcG2LX}+ociJdT~NPU;Y>=EF3fX?owl{tY#iA zywOdfE4f&`Grg1P4Hp&G(l=MV8B-)54%ulPr+-&R80BWNb+9>&X7YoL>GpDb3!Z zw(po$JH6aMnz=aaYrO-Ib%uZ8P@!p}^dfc(b^$i>`a@&UW$t@Jwj~E6s%3cbCt~&q zGE;SK+inDJ>`uP|7|sq>+CK?#ZeZ|7<;AC);U$cM=mmn1-K;Qh82x1$O}^2hB-=iHlGmQoszy63 zEB;sYdJO*wX1L)p(0c5oe$j`5 zr@Eu6MiK!6T4wn`s9v?U`Fe$Kd~?wi8zbBF8+yFo>y0_R7)BjQrUI&%Z?59DFbE|3 zjMKJSv$&eEE88W#$P#fwzUHevog}heEd4y6Q^&R5--FKg=;Z|psM7ch8>n@C`F>>( zNdOt4v`?K%jd{+ZpS?NIxl!H!YHHp+we(~nUPsC%xlG%ln{C*g|8&pB$7EFLX(^pn z<-UBSaB_uxqSy5({Yy2fZ`L4>mxvoYm4^4z)ebY)F$$cNH8w9dQX)+(^f_tMPjc8w z%O(zZYIB-68wY1H&mx;Ie%?9p7owl&UeOY_sVNJCks@02Ms~I?Of3*e(jSMr3n#X{ z4FLYtVwQxk&s0L&X3>7Fl|g{!=>U^qRX?NnoY-53$N98N(+vaFeG1_%Vw}x3Zt8aQ zzuy%*e){yuyU{=#oiWjes_0Ik9QBujHJdZrM0r;zl%)vASd`W zn=HKsce9^RX#71W(@kevGEA4v6#2xbTBX$LMTOD`VMBy?$_vLO`??t$e#uUq-Yt$9 z1#VS>x@-T7UM4l5p(cLqtua+H({Iz;_W2i!4Xwm45|dFYoMnKn1Uv1UQXO>GQWW;7 zb5iVx5!Rb^4z0bqkG>Tp2(%<{Lch)5+1u;zlA7G0y7sLRAe9}mIC*Kl{0s*~#KS?4 z^ZH@L1B!^IY?+_I?f2UVRCLoitz2I%hSd_gTpn})*m5y35rZ2pds}O>?UThoYg8TD zE$gQS!#Ay7sHjWX;>Kk99+mLFdiT*r1?{@8+zRl9z zXn*yGEH?UdSihVUVSFUnS0oTwp18}&#;2AD4<7w8tB(VEU7vH~J}B(_N77DO&FtXu zC9dbW=@*5Fr`e~s(*+S>W{9)Vvih8koI7G#8YML8sud{B@H^W|-SC5Uxtr3PZ1x6? zU&94=j|P&hZ!{h%8&pi_)i8%e>&Wd~e9(El2$)D(Rrp2h_e@&&uw6KD!X#>$vlhuQ zv3`d%d|^n|9QlR;5++1%qlqU94U{6iw{a1$b0%m8VM zs3rik#`5z#?ApDX$E+KaweR{c2OuOjgjGfiJ*kc_ktKC|SQ2)_`-_l&v)0sj^$^LK z9U={uX@BQ^^$pE%Ix*Vo1tRgAG%m`4MmidA0;aTsX&Z;1TTWP4PQh9RDG5Up9!jVZ z6z+=@W_sI%_g8zXE!w*Be?cv;-PX?wP?3Unwff5*gTBf-dUC~A>yVz zX6Wp^0ea{QNb(Op@(fYUW~V!{^lnycCKLhcMzXB=Tmg6jV42kvY)YzPUMarOUEdl8L8Q%BnV{c(!A;fqEkFl@Mh`o*a z*(yluuFdJ$mxchpjPqnSc>kALbVpE50Lgjl?p!$3#EAVej^_%>=PAKzSAZ3wFf6!{ z5fqkBDfm{w!Xlr`q}7mBQM{z?Ote z;m@z?T3MDNcoTaR*$79eF%Ri6d^f2ym;oFqp|$s;5#=#fzPbS za*0_~^S(G;@liW`L+!Pqmm>}%MMY5-+g@xmK3u3hVunRhH3iQ-LqH{ffft4Gud4=i z_$+@MJsfn@xCOZ20?|JGE0nFStoU~kt5P2L5WFS;cG9<(_XCXy7o zOaQzCOXwO%8-|>z?BIEh3zX2ZSD%ld7kutF9%=md*JRBo)!wr$HJ}uhUeoH>Y%@}j z`6r(d=%EHSGT@Z(sqOj9PSgn$Zc{Ss^s?nnz>{gm>R01t$46icncJxj~&fu^sYv@ znJo)517QCY<%XwebL?pa*=OK)Uxs5}KE4k}M0yJNdusSAJcbAb?2%@xbv>Jl&8=`msB61Ee5@U8_H#t^AL)r|TY5 zqxs7MLFPIZ`Sn)^`1ddkPPw2SWpsGQp&AN>b|@gj=i_4}Wqrm;O(LQGQ_xarHByj( z7Ka`e>rCP)KNG*b{(u~mVz>13J+?bDidHrsgH9QOasu6Gfur^xP$NkC>2ip?JU~K!By2W@q)iwu=hIh-xN({{b2T$B4SD69E5V*Z?c+po;btT8}vf zphaL4#^{~|upO`Iv6BW7ncai|zgG~(<`M!;Kl}zrEkGey93JTP!!0gPiNhWy`-87i z!1#ed&wSn>ED5f82_zG1S=k)TL;4|FA^1;oGJ&tJhp}PoA@9fU$|sFeJ~F+Z%((Y z^B9suD-;h;Eluc})JUaIy&26@BwW0zUx|(fQw#UF3`tIvAQ)QQ$R*(E;!_y}bfhcI z_?@F|G8pmEOuxrwT|_BQoN^?tFHyi61B0*xWeU@pYLXLl1YI8Ej}1p&tAqru^(Xa9 zk05RMhAEN^oj-!P9O{?C{NIySy2i%XmNgXkl|jS96p~E*0TTM0FxYu>Sa4V)RUoa~ z_)7sxIE~Dq8<_;_tnmRq7;@}aAb2hpcvXsK89?qV=I&`?QW>8CVzilI00Q->t4U$1 z!2~uoHs+|=xqCX%eLHJ668_nS@X>?1}zqSQP3+GnaQH{(QZcd-oUNhh4%M#k3Tag_noqrnj@Nq=v>Ie6W4$L@CEM_ywzn9j_Gpd8B<@{F}e1>@J*;h=c7j^)a^^ZIfv=H z3q$ga3#LX8jx$xA85>&WojP*cE5p)Hhg_~sS}*4JXN$SP^y$NW4ViFbiVg3|@)!R8 z{^3(xCs*XW#&(b-m-JUupE~ghHpT668_VAybPDicA$~MYSMbI!+wEuKEm!9|2;9pv z)CPhV@68s{Y~%GrThz3Dmw(aS7uPzj0b($w43wDbi3fE+CMd8q7lNAy344tTic`g{ z^g^xAkbI;{-olpy3#P08DB>B9QRejs``YDkb-bv0`mjkdU6Vf%&(+(9O?ljFNhn6$ z+XqZ?bV4<-rC44L&b2q5ODJ>*Xe3-S)2;u3>h9F3{fUAxnjEF|HD8iB{$pUQ!s z9QnU7Ndoj@9mZ|T;k24GXA|f_c>W4|*nLtKCX13?$xpVv-6v~V34N)0n*Y!SpychW zOzV5L;n#H*EOX6swBJujw@3!CK0Y}qZ z0MHuJqt+XSveL+y?lLPR5vRNVg6B#U64kI}FH|2vWcW+AQuvKps_h~@B}ETTDqnMo$z1|OM~>%x6!|T;lor7MsL3g?#{kYZ5ON+Z-NfCkB>^x3FP#%*LeKY44 zGQR4&m$W-qNaRK6ca?my;g1Ud4U}{N)&?*S6eW(Swzwaeb&wa1&Fg_k+itu0D%=h5 z*G-e=_V;NieKZOWutYrp@GUXJ7b8DL^thgHB#xnu3Q>l@q5G6+RwadJL*{ry(r&ix zQXEDiaUgv$mO{Q1D^Is26tiD;yhK>l)dal;1y6C#ZPzw7ssScJS^gF? zh~9M8X|iEl$Zq_ddWTTyfzu6Ptz>%k|X- zvP8dI_nsK7mt@;_B+E~)%f`cM9{4g*X|#!`NCqChcY}_&&Gc8;5z(5v3_xQRWp)p&HY63$5ju2Y)uVcgoJac; zdaJ!>)Y~?W4h-hocestoU7)1)^^Grecigk zgvb_3)l!%V?(L0MS89F9{0($9V>O=IIgMGS&#t=b#v(CT{Mc;-^*D0);N^AtW?Dky z{Pe`P^iRVJ#1Q#?^*_mUM~**5w=N=3bPo)&zXpiI7r&uI@-DQl=jRuju8`Z%DTM92 z%}eB#-;q#VG0LCZC)0bhdriv+h%$&o{zsoz$%@MZR40~CeUDNES1SeeB@0Y*@ZM4# zSU)Ljf8&AVj@>z0_x@-ni~D{dObKqEuwdN50T$SAK`)$e^aRyIB_eq(>gJor+c)*|4f z+J=iS`LDYa*p3Z$6sRmGZ0O1UdRib^?9C0xTyMHG{9#`IP@Hnr{cG~l7Evf$#8RshCl+puC zIsjb~0XC}nTx;49h6BDMj#vO(uw5@I9TlLi8t8&>091T9rBEX0yBMy2I(A%%T*dvguxI(KXy6(B~pn@{aK7c=p! zz0>1W#n`j^Wam$IfEYtgPA+{R;1V^AT=2g@3V#^2srP)5$W@7O4VL%wr+VI{1Mh@R z=0=Mty&cME>BelkV_PC`?H*HGo%^PBm%tR!u!W6YwoE(Tv26vv%k$);v(25nB##rZ zO_#BalA)a*CK$kqgRX=^FrrS&zj)Bc>L1HJ$GZ#2VCPQ=X|dk-_{x8nc6w9r9nTKe z*vWWazq5|XWk@8QvkJ^dn*LetJZZkX^z~^Cdqri_*$9Nghz?=<7r@fb=>R7|5^yk) z2#-BpH`>QzzZZTR)+2!}>UV6x7?$M(t4FZmbp}i1}Ubk2!aM zlsI%Z1b`nO`==wf@znuC-o0*DSI96yr7nivpVPT6ok%@@b-a4>D;za>;r#RkW}a~8 zNWc$s)cA4O-nJewQB)$z>~{SG1>N5H3e&~yKDFQRAC3=$eRR%%wbGqSq)oltXLC0p zsXfh}JUJJBtF3Pd`fJSgDp{NUTT86`IGDxAwhSWD0P?i$h9ggTtE;Nk}^Ne^IE zEMyri|BzTVQT1)zwQR`{3^ka?`V8%{VTx2*Cqk||`zHIkTYgp#*KS7Y*AxnUaN#&} zZSvlBV6oW{gAX50XZOH`A3vHf?SY>hyN&iKf>+W+ADRJP@nau-9D_{9-HHuQs$lo$ zCY*lXP#Z&aQvhF70^aA!3;vN?PH#-)H$->L#xGpg({^{S;$%!rTGafMwXi|lzq<1< zem3TDzMZB;OqnEaE64CXNTT(t!uA|IIe3LF#)xyGartH#m1~z&^fNkJ-LwlB3DDKA zlsC=Zos4<=G6L?lY?8VcEEgW;?X842FXFc(0V!og+9hAU9v05a&K1neXT(N(piyis zHIa)xd!bBn7?eud+HKJ^oOaUGUoPKNeYfp#pv#jDTlKspi{{eS_!`>G*z?H&XwKpblqG7v_>Ju zD1-ydB6KurZ=H}E76?}qZBHO&?sRxzeXts;N;M!Vn@YN7w@CSvrfI&_xwfdp!Qky)6{;CS-KlzI!MZh`^OEG{aMNTwF80<28FLb=@92Z?yKAc5K7g z(U)9L0y~n$>`UW)E>d(ynKt0^4`GeUe7L^ox4Mrezp%H`9i)J!aLhfEa}fq;laV=i9nwL+(*%Kjd640_UMfPks_E8x(=#}F;c5c!>mX^{2aUufcAoYo4EPkKgs(=o;R`z zgwlq{BZ9`@HJD#zHy}fVkmE*g+7lLF84v!gqJC8?d2747u_8jJ*G>3xN>5wALzd0= z5|w#9=id;f3r7k~kM39#WTOPcI=qgrgqq*o_&L2}b-HQTEqc7}wdr0HJTy~=k~mkw zr2x+zRc@O_f>q2yWWl<}kH;@2l%QbFbnRlzkKY~Bx{K0oh0wrvHYFfny7Yt}=?Oy@ zB~72?CGWk;r?aPMbWFS3TB6i*dQjPv1(-WfN5p@!xh$m*##Hl&UGm~TOechyt!-=| zGS@vv{3U5e5k}HqWkT@+=W94O#Qq?hid61Vy1av$LF{3mY6`V`G&DZ9fz4O(&_he#${ZEED-`a!56*+McGY2;5Kz13M7zl z|0Z?unStI#?{30UdMTrkp)tfivH2C(aKI`Lmj->gAn5>vdwqv1lo@MGr9FT$b0RY$ zBM_fD0bO|JV3S8rnutSOqEriKK z0Hx$LFfj6BO7(PFcan}dLj;z7aIw?w$Hr0W39PjpGK){KKwWOokE+uqh84$XIo|F& zPcpDTwBYpqX)s?5@pT>*In48olm~Ip2bo3!*BgFv2Ty#hPdzQ>H3j-KnZw_zB(4S7 zSM{{%`0Ke=DW8141IiODb?`lJW!1ZiHaUSx$af7L^=|t4-N^DN7QwTaPxUYIE!XZ+ zIp|T`SnqGeKdci<1N@ebhd9iE3yhvL3(Fny_zcW}u7zZ`o(VrA z`NaVNbMM8FQ2)7z!D@ubP{L5aV>P~JKJ4CY-fw5pQd`RXvP5kka5Z2EyL(@{(Q{o* z<#059(0SG4Hl+<1V6eROfh2MdE$DNm0*!mxcsXL`Zrr$D3cjuI`eE*k`_j4ubV~}% zzL9b)z`V0$%dVY$LcSSL#N`fg=5615XzqBG|I)pTpohef`>nzW-xA=91qxFM^Sr8% zK0!E2HLQQJ1vKhA->b6?#D)B?Cox*%!Xb(dK>?)G3p0qo5HsA6JxBi)0pooV)%#n<)j(;EsFXcJxD872wm zUo*FEyx!c5u>&*gU+JZnDm{N|Mj#9vrer*r{-64HOFvIg4{f5ZLSdB;qrvnOn`tn| zmZiD`7)T?8xEoIReD%`g&?BXnfS%u~xc0sIu@BBnwJ>wjz<#O@etspCYfwn$2NnM; zlSS^lfGH$Xc^bjFQsBL-Mo%erhi(I&ZmV_^)`5_I4mXQ3bq5%Z@f{af^w=`@QNnV-#-xKg8E53;#UJ+f043WhfZbW2 z;5(vHz*XZ0TDJyhD2&MAF#_+>JNB2}Yi!mzIaiK08vOM8on3kv9^bzEu)`? zeRI^dyydO#_sLA+V0qWK6+i!;s&@B9x8u=OCj|6%;CQXQw)AA%s4|&3-trEFh`>l? zFT-TKH+GTzv#c`x(^CRwn7=3VB!ml#X~6W@E{A9${iJ zrpA{t;9afJ#LKp&?gLVjKVRE(iN6~IusK7gPO?zg=D2z|?30oL04>7pDGJvBUK)>y z?yU@H>fyQ>v-$m^yd-J&9$l--&9G$R-pRX%dzlmbG(Hcv*geMsaMKmvAB@Nl5Ynd{ zxr_iZDxOCwecsdOrugR-Z%2h7(8A;V*@o}9=~O68+GhB!KtQnWUQ?@a%nYEcAVm+L zVgw90%>8{H7>&132#$gj%Zfym>0=yoR))Z5vc|{IXX_gKSIa4}`lQI; zQg{h;(Spja@jVd7y({r1D^K>{DT?_*^>ex3G+UXTc8js@TqJTp2J|u1T5e)Fpt?tf zZH+K+{P7B^q3S!wK;ED=AfCAF>Hj1!sLB>-njx=_Lzu^8^nvxkee#(B`hlK^P)R?N zctBKCyNFw&pB+VUGx8)EJe6rXeJZ~CRkko<%J(fHp4@`odF-yuK*G1Q8!ko|)-H>( zev#s$P`}ekHNVFu8CPbxmj_cU!ADu9D~|z!yZ{a`)y7~d8-GHQ&!+VekWt>nTTvx2m^_~ev)N>N0`xsX4VGhBQzF5NCTpU}V zs=y(j*qFRki#I!luA1?1V&;~>{_0aoaRHOABR^;jiNVanNDuOE1_!MNLhq4x^iQc8XHIGIV&(1ts;lYhzuUpr*_j z=ahRJ>D9FT^KhqodqAdC=hNYwK-Dj%^UhQR4Wf9bqpF3K*#*WP^P)|MEB?6`Qp6tN zKG{3^IBNNjtky9T*U-6=gmlF*x`$L3nP`wn$Dlw9Xs-h0+YmEu61#Z0tD4XlkoVfB zv63|x3Nnk`2O241L#H3mn?vsX79O7!&aA>-Ti)QHA7M&Aa>Mjx(VWj85kt&KtYW2e zX?&$qK9fg($1elQ=A1iBY*x_$j-P=tqZywxwV$SyN$lKzc-h~*(91S3_G**c3#gQ7 zb$*s{c{HFg<6#&ZNvFrGQflhuGQ&ftTnEff_QYU6m*THym_S^?*! zxcro5TX9d~O$AZXKaKVhzyp39I#qapV~&+r*xr?O`e(Wr7SgvCiekePqiGZH!|(d5 zzcGC*{To&IW!K+M`nB(P^!LBeH{X6(zFM8WXyWb2;6h{6BKinlwKvXWfIBglq>l_{ zFa+zl;4H~+U~{ zb3D5%T|f?9$*lu)+@0gD&6k?8uQ;4FuA`o~^S~q=Lww&(TDiPtufQ*xlCnz3T&Ocx zFkDOW>-Gi+%b-5~o`K6A^O1w&Q43Q#Z|1=lP3goK2XML@)BmjKCG54{<9)Nbhu`UJW! zIa|ZcB`BY_E<$|HodB2K6_6*xNDj3a5MzGb{Ns5WDPlC#HgDIeSQ1MjeA67_BAv=V zV#~%w_Z4@R4kn+oScMdvv<#3!uX7YVazZG^3|-o_(g<-kQvLDatnHngH~wR>=K3(0 zkEW6&{sl!2;rpBj#WnJSC^moctoJSkKdaJ7 zRQ}{1VQ#=jHk`*AVrtRBMl*Z^5kP!SFKf_Yw_|; z_Uc=Ck~g*@lfA`JrR$(1#7Ua@G|lhn@R0EAJH7WH5~@H3u&jRhHjtx>2K7EWoxaym ziR1d7VLAjhbox-?$V}i4V2C~|;qMXSS7({9MmhH$wJ0GixUW7)oH~aXqT7x&j3_?0 zJeR-YtC=6P6WX2X{(e^^h$5g!0WOu@Q?_0(=~$Mqw)2IVvyN}2&Dsn?3?-h!jre^} z_TxdN3!qwn3Kmh%(@2Tv%^5*@ynOtegv$HiL*A)3wJGY9{wSR zxXSsvNG`F%u52C?n9faZ2jSQ@e^o|6$Nzu;;`5w<5I~nE< z^%fFa0{IqeBK49j{}!D~1&Z12=d$@ow}Jle#CviMUgv{v8vq2A=mqM`8F6b(z*IS7 zbv1MqQxnHuQc+1lc^smwP)jP!dPn_KhbSfdZ~ zFgV;9bia=bYPp+2oOjGdHpHtQF{cFjeb6d2&4usRF9JA7ta-mKZC^nRNja*8!XDlZ zh4~dStlrbpv?pkQR=)u1T(Dov8_1Abn`X8=pY8nw@yeGuq1vfgtgvA8i(ZlUUK^1c zbg%#US&@lZvw|e{llN`9V~^L5?m|x>49p;ANai0L=1&aM3U|ynKe60>;N@X2{Rw3h zx20oGtN;|4N=(@i-b($oa;(^B1}V6P4lo;mYbHhM2xWDv<+$(cpY4b9PP_uUAv*m*M7Np=iZxD+Byc^{w5mV`&Z}<4+-y69x5QfRU0eS_zXVTEwKCnZ69an@5YH{^2RJnB&9!@erZlj1#2C7 z1T=f=-1$g1&Z_0P7OY00q@d9Znctw`|_gi zR?yC}X2d|9l@A@!_4i3$YWM17+*E)u!F0wkSdHgNrFtC8!%sbfut}gKS@*sd-g)Wo zCfggMR3Cai#7Pi_+jb&P6dJexyJB!V(rAgI zmz9~?Bh=f?047Q-RO@Q21R)4RHq-VYofT~lR(6iHd^2CW`Qw+aBgE?(Iu@Qex~&_u zx*6cE(_6M;Z0$e0PGtH`lg?Ab)@DJ=07a@pvr6me?GX%o<2EECcO`IQShhA{1dbQ;9=RhcOChz zxs!mFhODo*=kLjp5a<`1jBVDUK0dkaYs z?)28Hg+5YY%^LMmV2%3I$DY|6??_`nf6Hr+xZ{@hGRd=9lDv<7B8!Yy7ack?0UC+s zO=BMJu~)mY;k0z7s{Y?IT_+m6=h@anUdVlvzdhJ$#gFr*@=SC;?`bdGSe>e zusqxUMNxT6w{OJx)vQj#u3D3Ya*F)k#fXBQE%b?<;NlO565=n?0?et!!5T&oVZ{O5djb>HOqZM`RGW|t(O1(fnQHP3rX{YTC0J}j%VqIUuwe}-T zw#2?hWL)C;caq}{X-(T-H$xbJyP5*aet_?9t-@NgD(Niicokq7#(XP;rbNf_8s2aoBu1v#WHzaz~6&lB$+j7{cw%)TJ zpw3e#q0cMT(l+bAldwtQ-y-_8D2)p>IIGk~OC|BvJV>;nMnP;F1r@5Z=7%W#ubgM7 zon9v!u2-yd6Hfz4d4@$ zW(*8Q6QhPFc9rf_G20oVOX$AFRx!p}pd;e}LN2pbd;8D)`E=|99y9J^@yavdMF4(n zEAoKf4aieTFAl4%02ToNz$qx+^Qr#Ov-y%`beJZV1#)I#r{~OyKK{aI(^(K`==S#G zS@C%RO_6K^(OF*gXwb${lKQ!C zcH?X~R!*T(vNYN-<4d$+W2FX$?w;YQ(UP@uOyV}d?u!;@hVGB*h=(Q}1=ymG`mo(3 z&jh^=e=W0$OAXewzIqKu_t;Z>?k|V)Bmmq&0+1*A7Qv$Bg?9z!(WV@XzOBWsNp{7>W`oonm3Laqwsp^RDPQzq6kPe^_A~X(Npu=-e_5!*({)q1ch=oB zsA0`Bb-Mx3>ai`|-uA;}x0`l9**RXljRdGO1_fN4b;{SN6txoB7_aqKMDS}3Ki=H# zT%%>!$|=7X&M)@^XzWrM-T6~=lWLM36Q50U&X}2 zFZdGfN-&b%g%{=pw~?z42ZFqF&K{hCxgn&emS?V`TWV)l(oZzJiB!y?PZ zoFU6&))*RXDq4=x!c*PKHi*yBN#kPky3{REv2mvJ$*;(zjwpZ8wGD(l!FgMfZcmV= zuk%#1bIMVaN&)TBCHa@Vi+5jvxLXrgAoSk?g@E<>0qKy>kBi4GLl}(A^S)(ypgu7d zJIkxnHSDf1w2F!R4#2pkHaE@kUZ@<^1Q-;!+&1>{Px(yIn7_N}rhyt_uf45C5{YGI zG(<^9NqEd}!gbYMIDo|s8IPj1-EjR7J~dYdhctNkNbA_2AV8x#QL3NPKe#T2%jN?}0CFI> z`RCq_(k+>t18MZYjFG=562N&X=xMUS;?xyZRZv}JrfLGgZ0XXc$+#}N`>g0V`Ky^n z*XLQSF|Wv>dBfX;m|Kt=&Nf$;pCY3@3xG204f^NI9>e%omIh=F6JfgtO}KgN2E z4D>J_o9c+9&t%@_y99M}XRa-xcN_8^KTPEHWfZwGMqV?GEPBhq${XLHmISLnZ z?JVMm&zne)-Jfbn2sZq)e`Ej@F;&e1kJ|j3b#xqw6@E1*i@S&KURB?L*Uxf1LXc;V z_?31rxn}g>q1vD7c1${orYBUoK^6S`I!u#jNBO-zcbBCBVsPgC>uSdN)PKf(Q=ZFc z_&KK#Q_E>yLfOSTK0sPY??DTA0cqW&!BoSreis1I&&$hdgI{`7H(Ru7_xI9GSNTh3 zMM0JloYv?_pMErSGKm709HEPHTih*HPv0qal)`H9){;mjKOksU;pgQy02X7s5!nRM z=$THXBlXkV>!L(3BKUBd+bpk`)!fmRB+s6c51s$2{K=vPaWVJFk3^((qU*?_-`7R0 ztKSRragGi?Bkb=_WIvRB@*7u%UG8}$+09YLNnt!gu>_$rRh-NQB_TjgiVH|t0ltGk zkeMqR6kxv_e;mFhIQAXs-zTtSZXDfDOu^3U46v`4OPXvXORNGcLSDM+{_bxT!x<)z zRqlEHh8!PGFTZVAPt!n|a+_bVFfVz>N?< zlqH2U!+OTR#U8u$^5%^TO*UbkUdS{qf|yhTfgiBJ4retjUDtM_sDlqzwQ~ocn^)Y9vo|xfQVtFQN}1I_MZLpa1C_&o z{pucr5`%nc+5V?>G=c#or4@5^6c>UF%>fM1S6JIU*gF0CW%?htJ z^}n*f*b00GE*%2>^(C-YSQfBSq*kSmfq;U91J@nk4$EQC-JK6t*wKxbZeeE7=`dB<>4DD<;GosY4RO_Zv1h0Kle74R69uy3u zvEtMCSI~H_GQ=t0DQ|k=?l8-0Y3yw5GwpqCLZr<&l|8ndu2*#-dZS-~rcqM;*j*4rj64 zF3tP)iX%mU?TVb&F~QM4Tl$2`gv-E&Ise!a;$O{8zdz5f+66o4nIM_$!CYHalS&je zxgx?bWDp5*3W|OJ8>W2lA#!}tbc*Ug6ng$Tf!4cu3TKSD?UC3}BC!k|2NmAJ>A*hA zOk(R*G?(5{t?$)Q{48z{`}*UDkr#VlK=XD`VyY#;!^7SNJxv6G!jd9 zs&t2RH`2WV(%oGWOLs3Uzq5Rw_qv|nAMd;WEF$-P=A1J#XJ*dKXFf~ssEmWdh0ek- z)a_RYJ$=@U;C4a3wU>o42Bk;4O`N8{c{$q&NvA<%zt3@TdYKXLt&?Fpy7q= zNibK%FHS_*X=!ou?Ak#+RYlsW2}yq?%q%XC*e}@rz#Q9(;r}xOULnphs(pI>A*5S%54RA$M$7;Vm|Bm0iKraoj~0|*ih{lpB(pR z=;XWWvI@3OI6Sq=SHxW6@Bif1bT8#fmwL)Qo#_kwaC1Mc*I52b+x}c_WdV!z zY7SNDe6m2d^#*_ZuH38F7N>evUl$!`zjaJSB8&C@e^;uVpL|FKO__3*mZRbk)BD>Y z6UY~~YbnO9m6|fCPoxKCe(M!Hd^}K*GifTMt?+JOWG2PbecD2Z0{aT&(G8Ck+mj2t z49ei3v7-tt7r;JGkF^M|XRB}a4|PuQaK0gRnL9r_Mw7)mZ{>5>6O-pW8^+p()u(%9 z`iK55@$-+ojAMJdq3Hr}g3YX|bO-WIN11!PQvY{kkS20SeoKSFz5PLi7p7EZjBv$= ze<ALs=N1p=1xt80*YU44SKLyDjcDW1W34ZD+rBE~q|N*@zA-7d`ocmswo`(v9notK zNOG*eK954WwwS9~xvaK?mUwKF=ENOQM_EK`8jPE7j8l|j=S1vRV69E>6lou(U7yuu zu~;zWj{SsdyL=ar4~WveIo@0Sg(m-X;hml1N#5Zw z0IvUL3N&{1MzTXqW$062p# z7ff%zysMf${95GOYYSU3lQ=xdI5ceY_iU)|X9J#|;n-TLdBONcsZEWiZ`^$)%lhzcsb0s%keV^<#`=ko~~p`ONWNx<9k4@<-f2)w7pQ zJ4L5Uy&vGB+r^mQm^LVaH`@YIqS&-w{Wab1EilFXkNJgXUKHmsnL4 z*|?p*@90zj0pqZ)^_&wL>`RkB3UnYQ`S6}>#<94}dK6vB3yW~?Urn)RTQ4nyi5#p) zJ$M%qusNz_>w9nb;!707N{|#eXlq>*4OUevaB*B@UOo(=pi75+@2Jepre&b22gHs{ z4kR=G@3@_XQ)FQaH=ljC*~b}W8KNHfKZWYNzlJI?CXDZptk$mPrkF>cFyziU-rRf6 zvfbcYme2L##<65dXWo=5K+dM9FNbYuSz%9%Q5;n!N?6C*OozuAwUoiTSC`UB&*(PO zk6ycS;$X0cmeW&pl4!w{XZv>3nfks zy;bh+t4NqWN%ZpkIc%&|d^_t^z zF23;7tcHV>lfx-_`G|tGsLpG+c?4RH(R-TF>Q*l*eU2K;jzUKxc4k{s(}&VN53`SR z$~s_DH*1ea6URB;vFwudT|;@G2^OFk)upS!Q9|%bZFuYhcm82>p{LTS>-?YSBYxcf zLjCSc^6$Msor4E-YvgL-jeUD5DKJ?h9b}kMRp%9>3 z0E!UudbU7a1|_!rd2TkvE7@JglL4C%bfHK?s|LEQ7zwx<|Kh&Sj2cu#7LH7d}8rLLk_K3Nqb(5gY6wjHSM?ENM>|tff__dP^T~d$0#Gv(R&PY*{6S}q| z7Ze0JSH5BI#f6G&H%5V6CV*_%@P8(DTmO@1@!oxEu@HJX<#T9}dOSPRceq;M(AnEq zcFDokF!OLRIJX|<)f$pJ}5n_b#+~A-{ z-+U}d4EFk$>n;PpJKs3LG^_guJo-DemxTnVK50k7+K2yUfOY6^c~YDFXjCw0)%9aO zwW?`i%di35DP*$yZ-i@g619@ zdlb}Ox5{N)gp~uZQ^p+DPUw5RWIkS)!Gcg|1^K4iIVx@B*M9yX@4Ouc3aQl%kK~?2Eg-R$hEyADp3N>Oq?^01)!D z&%(qn)z?rZzo1muj!YB>D#S1XYyu`qgvrJ`#Ov5O#iy$oq%+A6K~Bhrhko9N-5#c^ zSShUG%Q*cp)E10wodAvVm!^Ni9=aw?l{000&q^8=zOXEA%EZ}s zgK*qYbq|4t$V+@)ich%dA#3BMZv2e`xf>@AqrNLO@@w4ne8_;qf?J$i>>>@QZ5x{V$Cb_vv zEes1Fe|q=W9YLpZnua5<)IS?~Ks{;iE+w&#A)fUG=zTkx*u|mn!j510%^5x)q-ZXV z%e%ql=>jr-?R+^jhv3b7U?oO{+zMzPU6v!i&bQje!!oC%ajOt_y(U>+$362ri_L5N z4(g!$GL%(&6f6I@;3fqd)mYJw{&bOZJG=*oz!ViOH+@MsLgN=Ik9FP*_XiyI2iUKb za7ijn3GcRwZ?Bj{cLuE9P&|C5I1XNc%{aN;ksD%})h!rA z=}t*5$P5aG(XLiwby-~=7PQ`xP?ORDV5Uy$L&3yxpz2bss@ti0Cu021p8x|nzog=_*~Bnu#t-wQ z0!(H|j(&=Ly0W@5RVcUPPIc(z9hzwDQ9&7pyfR#J4FDZWNj-_XPESuy*&@kIkc!)FO|#;r z@T3RkF3_&J&B+yzWn+9vF?!#WI=ftXI)EH{Gl#TerY3HtD`jkve1-*t!8OS*th__O zfc35Lt5@qeSsq}~K3w_H&~GFZHT5lc-Qi=0`%_6)zDDtJORPXXv4Dfkk?AJBXE{_cj04gidqDPN7i_{OaH;yl06&SbLF!fMc~A-xFg??LtasE}LpP8wE`%M!l%{KfSi0}PZzS;q+#kZ95^I zk!mWF(cPk?r#dB(q*hbJF%y$SLy_7pRj{YO$=on*R>^MD_WifU;}6Tur($?Fe_nOK6r185ctA$2C&(4PeBU3{(Q(`R5eqQ=<~C@*C zQ!UY~>$nQ#eT47=Ew8D^OF(MT+l!S*^d})u-_6zT_wqf!m|&)JjaRuoZxNI4_L*1! zMjx$!dAGI^g7iuIn9VVtc# z_7EpJqx_5*f|gMZVt?v68e#md?Tm7FoPBDjOq%1j9oziFDlX~XRL#B|K$=E1z^YUl zOazAllX@L}bayXxYAHC{jGNxub-~m)jAsFd@z~kzW^^D=e*wUW9-PK;71|^T+z|dl zsajCfx2k({NwP%eUx_K7rlsu|QHZVF1w-4SCnS!uR`3xOkWG544K<%-RvWkwf#uVV z<$l*+Z8vsjvt?@(9^`D1q>@tr6%!_ck0rYB^EiA4C-ucr?b*fhU%|U9rAO$ZdiafkJ8gL*Jy&KqljCWW{ z7+NKgmTc*9f*)`Hm-h6EgvvR;^~5glR3LWlv%H2to>)!&h=L{?2*hbp)!Pm@8QRA| zx2vKo{mM~5R1Ypp>sg7%BFIzF z%&Pd4q#8-Nb>`K#rr8oMVOeXYQ>*=YcY%8J$4p}6P$X$NZh#yB?7*f?!rdejJrto_ z?|evGQs^;_M6fq?MdTUv3yR0QaMmQ-NLe> ztk&79z=BU7aY&(pA!U{u`8X5WfK($*Sa`>t`{2Cg__j~q#>bu5EhtBBZ)Tf0Y6?RG z?7pFSUZ_+dkU>5i!kUX8Zti3iEeG9>fFbQN1%$$kI_@Hhru)OLj@nX&AwP={0Xp_b` zAuTk3GTF-<55BZ?5d8rehjU9M4@^1?LohDabXDLH*fV5*&2TV(T2Zo?aXr}Pr`!;R zi>a5*vz5&8YhWJ9JjkMbwpIPpNykf1ad<$GLgRooQxylY&O|6CRrG?t>6pfa7ygIP zyp+eZ@|1@={DRnTa}oyMR58EN#uvQnBL*)WAK-d|VwFMEO%uIw z*|JKajAYK+XTj&8NI#I~?id7ltRr~O}@8ViEe}i7s zo@&yUIooc^K?_%}5(~U-)_~CpZjxN)e_HKCNI5#n8TEoiBohUNU~@g0cuFK`wN_o_ zJ2@=W4fdg)rFq4mv)#^Q=@_z?jX6p@=uNY}ou>U9iapa?OLbk(-}7m?9I&&^n+lpJiffGcc+F6>>A2eFtlKrCQXxb5c;xdpTOv#sN;Z~FOoKcBhEvt* z>%=Ds85|6geRC|H8;erOvFRx)P&iNkJE&+n^%&+acy& zD?TZ6UBq-*=+z-yTC&@nu($cev%?tn_}{TzL6qBKkc~h8TV-vo={PqD(2I&m32azz z_)piJUQp;??}zA4Mv95G!lAJ0j*}+q_UES+ZmnxoTftk$f|9at(w3jTpQ)t=JCbS! zWMwh+E&*w4{Py|D*%mzow5!<#vEn8Dt`o%0P(32TOD8`%;>q=2SDe!w?wZ1`>@2_k zg4SGokkvZ;mpHcehJk(e5du$`_X)^Ew8!ZvzS}xaW~kP>>#8~QmLa7gI)cnKn;cXu zu9Jd9MT%-w`F?&5!|kGOf6C|6W? zl(@~us=0gf&C7L|!oVIG#vRKY*b!?%wLIuH61xf5VpxP%RWv)DJK1RpDbvwri~;!U z8<>?aVn!bZ@e-Ouu)!_x2r%7Zp2#2 zJ@h)I`nlDptYXd$gBlvkRQ!Wne!b0=@K{Za;XyH)2*z7iJi)udH~XMypy`t@fMQBb z@o#P6Xazb26nQTE2TBDI?d+96m7NcwV9Own?woCv%tRJcK|_1Aq+{Q)6%W|Z$jc9(* z=Cb6+H7b6HFKTpKa_Z@Y!qpP?w{1L4z^2W@C5~Pv7wtU0Eqi}u{b}RvQQfSEm)Q<-);4=%_mnjdZiO!N7sz7^Xb;!nijUxYq_Spd}g-ayvtwaca z1jHr`Pc)ml1)wsGi2NORPY%HC;(1r98`26Lv&4=z)g3P^N4CPwl?$M0`48ie}EdFF{bkWCKr!XX;eYud@P8A@r$lr zFMUy?baA01G=I0&``cFd2ed}kn9m#;jM|S&4C+WW0>bm!UXqt`Jfw99%ZEwd{2$%t zW87`Ul|B!o6GyGc6OP;Mr7c2!CnC1z8F0Bip?niDQoiH|@~?pOfUgZcg+6L1xVh#z~v-PZ|~# zlOq(|berBE(t>fHJxn#*gB>MYU2i;C4fYUxIa6(k2LXBy;z?u-x8z{l5l4jZzv0bP zAjy7x^UgV)z-ArfuK1rvmkKz{8G883(5x#5m_?`0-1DhYvT5}f$Zy|qNpq49N`jB~ zNAh_zX&3ff4~lV_Z=1?#Q6?1K#dFqi4U#O5K!X%FM(e5FP`h=W3 zWMu20$B?)0z5!2DvfY3^d+&X2A|Pq~lINd3*(pj8i1~APV6zsl^ElY_k(HO;SSUZ0 z{j;8zWBDVcyLDva0KZV_pA*=AH&5iv^m>bfg z-yu5Y0FFa0z)H$-38y+Aa6W6KW=6($m22>;A+mP}lLTp-V(7?!Dxjw>g>&BM-dMERr#IDZ7LMa_>QtCbSADCiUN@0YB@P+U8 z?3q5N_1)(84fPX?4pGrdkob2+A@#Gxk(i||@z!dXXD zteP&e{Tvxu^p^9BuL>pqNf{Rd^Ow%c6ET@D5X(2^lkRqV z=}`!z#j+`4T&PgmeO{jX7s6O1?CrmdT$(yLRau6^6Jh53YoTHTto ze)K40?R@cqxed2pDlcQ)YeUDQ%a&J4f_#Qmn%tftz>ri-ua?>vdEhzI#97{UeF@26 ziA;&-=i`xnjaXZdg;HbMMRU%%6m*=pq<35g(72rs22kDTwIF&k)ldCM?eB#{cqwTO zkobbo-!zL`_}F(_^#+QQ{%($S)sI<YCYgW_TEQDxIj&ym;;_ku=G=7oF55Q(^5zho_HtWbCz9GOrO!V6 zru*ARr5_l|_PP7=J z6wzYPpNODAc*((P0BH;K+0){YdqnZ|I}H;*@x6fOOP}{|7`^|LA_~>}qQmrV+I<;< zy9P;=PW_%<_P)e?KrGnZ87fzL8Q@R{zwjJN*}KRWyNTD4xHsf|XRc)D@(Y^K2f647 zWP`%Y&?%8<-BFnpR|>nS`g;eQjWgZQHWr>c72s6;fzv3_{ge93(giNI;GoJ_x?ryP zObzb^`vLU2>f)X_HJA8aBQEvzK9+o_$(+0T0gTV4*{@H?!~X)UlToyDmdXcsSyis2>;&EYu!gbqr?!^x;CF1+}=N!TY7+V)7*CR z;utB}Dmk<#&oLOH_F)rF2&=<{TnJ;t^ZACh6=eum$s4zuUT(+4Bsa2b7fmDW-G;fO)XJOY=K)Ly-}hf zC}vDZb;J2*ATsSr^c>f?qB_u{xC(ybb3_&27O9)eMzshRQWBd9YUkQCw;2;IjQHr}UQ@giiK z+LPxu`zi$N_&sth!xAc9!*z-}&k|d9x8O~cx!Kqul8AF#x+K^|j7Ht_+)-JQo9j1Z zt3)qP2jLfUu({Dq_jCBJn`3~ct{wRsCL5~o?+e^XXzQ-Ra0v41Zqo9q7^B7FXVbM0 z4~1lj=)sQQ3K`IS5JKZjKP8&ogqNHtQa>hAGUI6yU)b}1WmjBmg2>M?>W|TeO+C2^ zV&o{v`Q!ZaE|Z0_D3k&i$U}pD#yAAR&={Sid#{+=2ishUAFZ?7SG*EFp3)92=pK?+ zrMuJ_IFO*!kb~PXUk+aLa@$6S6C0U%E4+$ax2<$fDv?O3x9Qte{fTL}n|AbeCwAJb zao+KT;R+?Mk$?7Vtj-;QxU6*!5DEYYIF1Htt&BY;wZbff&k^8}yib{Rj$LaQt9*KHGp}VnZb~SlSRxIm;PTV~;Mx zqWy3TG)N1}?Kt79I%*EKd`hO9m+*Y_o~J9VZ!Z;yVdUa!&9~~P+s(=~gqQ9LuspiR zv89`{b6=Gt%EG#`OA%aVi5sf0jdA)9iwLlwv`1J-O3=GT5r;O0lmplG+_O((!9yvvyCZwixEUFd zcc0^(>Sg=yqX`>lFdEE&CHreakZ>e4*mn{k1|P1CVSC(VR9nSNR<53135RkB^C!X6 zm&tTY%JM%Nkzj9FGEpiEr>}#ekNB@|$gs~89G@h)wtr+Q{2r(}`%T)WCLm^LFBF&J zEvZz3G~T&~xm_dw15El|_x-onz2MPU8z~-w2*(W7h=IpfNp`Oy<`k zArOqA_)mk-5DJk7cH6IYN;x@93hNGiMn@OFrlpd@{5ZJ-O7ue%i4IFJ5l~5@{|s=E2+E2(e@bc0m3#r=(?rSzQ~_CNO~H7hO(r3hZc(=vGQ2FXxm+?xM?$g-*;5&L}v? z`QEosI$EpNv3di6DDZSUeIWx6?4)7+R+HP_0F4y=whlZzT9G!Nquo}l<_(zy?uC6F zLEKlHDhQAx_57Ei4k{V5Cn=d!R-d2(Uh9FOx-&hGs_igCY?u z+Vsyni*xo5Y6IrW*!qpv?ejnuM(H6n`lcxlZTqr(Q*g-o7J+NK}U#7V7r(lLMlfFN8q!Zj!i%897O#(VjEk66-xY!I;n= z?d|Uf=nYnz>dCiPxtYy7Ym)D$mQtS2X+o{f5~~(d=Jg@n6F+l8X1GJF%0%V{%5LVb zU0ELchC+Bpk}a&J*F4 zPInYn?B_D?t)F`}*fd92Or}I)v#|E-K_}_8 z?Dh6m$@JDhQfaNj37zYUk3i(Lxr$9Ia&S4PJnFNjAw=07K3M1-rzzHA0GIwp7C{6! z+|*ny_MbcSm+#>+n*WiQvlx_|Uv0Ykl^VO!ofm_O`sRe(QfcO3LS;rm>_U^-81QT? zHyJ_IzDJX(nrm@_N1)f;Opo=OoqEY4OVVp)NG9_!9_qccig>jS@R=%?E%MXNf`@#z zpSk%EEtb%BM4ftQGVwT~kok;Q_~>otOt38UJdf~n(>7;YYQJ)$$oG28*k$?w>mEp? z0uIK`=}HEp`D%6O6eG!T`R-K)77-Dr9ToDxVaj(aSLyzsQks7lSYgcg7O5mO%4v_` zWGqWIn02>~U`r;ar-RYW2kze^f3WIj{xr`OLJe^Fety9KPJX%=$BU&j0mf}Y-3@&R zrcVOzj9HHP+RfS?fv}O^W0VL&AW?!0X(9nKaXJ=o7;G4No!bnt6YvWi5IBx;`{o{e zN7xM0LCpRlb3tT%^bHdxIaVDUvQ3J74ig7y?S5|P7K zE5*yF+w8JjS6GcjXQT$x!p4`a^5jKJgYOeBk_2UV(NgE?OnJC&BVA6w&ydSyT(RP6 zGVwSXYXWa)9F(!eE#@(TCoS}0*JmH%3rpHVQH%_9k2}bjV|S@)G)cU%MkoH62apGTl+KV+gUX<7N2|GL1?8URY;dEN+U;SQr9n-QpjzXNVSX zTuLR7*L&Ay38gnBxnY(bZ%ey@0>Tz6zO$m-B!*HoVCJBXy&>>^I2!AwrH|ktE7(`W z*W8m4k^S@Yl7&*#In}PKW4K8_1SRG6fF`Q`~t5zNS|PJdovm zaj#oXx34pj_)p&*B0&a^Cd|DNiw+D838g~O*4aIjA!#w+-~{cWkDUymGRa^vL+Myn`_`h7AsZ~vtBccYyk<{@ zG%qy*iK1r}gigTMOD0Fx0m5#{nn`aBnFv6csBZKF=G-SScIOmmIns;P}4* zYR>Pmr!OkD7S%I524x5VnuIrQ`&uyR9q9G@&0B@vE=d;9fPkudd9Vy-Ot+Md{)EpA zYU{WF2UCIfwu8inz`J}Zc=-_u9DgsXW-M8{(|Z4-0Q;5#dYXCoZ#;&$F5+9FiMVfx zewwqWcqa)Et>O*#3n$~7Bi3l^8?@qZ7e(T;;MFMDFoblUO! zD;D@*@a>%ZP?2{?>Yp~oH~?=Fqmj}P@&mUg`*sbMP>{?Jrq7t;$|vhSlGx?r)@I(7 z$N$aP@pi_T3JnALwKA_Fv5o`8jdQh{W|=Qf%0GV0egdS8@|LtaijLXwMHz&>gCnK- z-IYQHtNlsO7Z^EYZf0#0VQZfoW$7fV91H)gD;7*v4_MHBn=*{SI+(zZeZn(HpshTm zQEL)ukL3|6w{HJnj|~V)&@IJ~bHPx|UhzgsKEM7+nHkrH)))gp@nKac@HvoCz{^HT966N3+(D1tU!9yb=?>yen#otF!<{q7@ccnHS`+nu6N_rF`Cde~#&>3K-h`G(tJ7s$(6Ss!w z@%;fIQb9It$cAVQ4UgGS&+AVC%nyjKF5qo#2xcQpnvq=>gX^wqoUa}%t(yapOM;u% zgO|)IdQb8;`O}u|N@cp6%o>{)3b7y~4Ff9d#&tBD__MnBzO~Ow7(eMgbxm?E)O@1! z4gi$wa5|*u)H73Sy6DzDmkCxYGri%meCB%(Li7yPaAHu7S8wd)>dJ3o%d)!yjQsJ+ z^g(jN^7D=v#8^qbW;Z7qgWrJid#&GIv#;1%F!ocS)#o8KU+i5}M;-3BFEoV`QJ~VG zftT`dYgwYVet7O-zZt`$BN1HoyPXz}7V^v_-Tq6w;Z zFdxhv`JAbFEqq_R(M~08fFYmc@9$-LSG?ljBjw+18c09!TsT|5)TYT#TMGvrk=pb= zTF=^7O9ke+nb&PHpG~_hP77e1Glx*xZrSS@^7uAB{i&a-6FWn#!s_2~kuRn4^~_`> zLDywJE@sHRfQW7D}^4Jp(T`l6Ad^Vp{P_Uz#_3-5#2-7{3GOX|+wVrEp?mC`M?B_}uefP{cz4v* z@lL>h8~7t1KL-LJ|Ka1|HErwXfivqut@?2{3rro@-Phc`4&6}(*5m2AE6qNAVo@q> zlnRTh<6|YdKV>g41yoiyYV!0`@|o>l9E*ij7H8*uEHYXAv&M)n($F+u)NTpeWAui- zRC}4HH(d88qq}?2kwQo(3G-AEG~%*e5O5HY5*zWc|9W#(r47kV>uc_lultxbvEU!c zZp;Onw|2ACZH3ngtM0{$j#wuiEN#o3H$5v5rrVQN!NaSc$XMiYo!fnVQ&&C-j5b~r z>NI30YX6Qw;Xpj|y*cYY`zwPVVXxnxLOw?SVE*~^PPW{rwqst?m*(sIrf&jYO0V_a6!16}-7BLWB+X-A}Td ztP_Gh|NImC;c17cuHTfR`$GMLnLR3BE96?J`~mZcBXzqB^VNc(gHERc1WA*iN_e)*T#TVT0RB(POS}U_dgyyCwZ>%T+6@^2yg;L{_D$JSisAK3 zPror%r0>VZ_G4GI6T)OXhmT)Lf61ubV_Xr8D=b^14H}x)vz#goNSuj5X6OZK^q(<| z9C<4Y^(m7J;E!=-2+XuxONr;ZTp{ZPjq-hmpJ9ia(pGC@lRPSbhN(9U&=K|$2?_x* z%Bi)5PiHS$iL{oT*uMCV3^Ur(vc~YrhZ+hb`g+@tw&+JCrS)S!~ z;t^%er7W(fB9_rURBNA5>7VWfahR(KP`RZ@`e|%UlvG>HRN)Iget6yMei)fjv5u?W z+ghfbm7Y&_jUK+#I(OZ~XyE#kgqo@9vC)PjoWW&CBJPt!O`@`dic@}i2#%BLO$RYc zLUod%#21Dx_n3MpeZU9ck9qa6Ym$8S`}WkeZ&y@h#UW3rWBr9k_0_;LWZjq~%3)H- zMIHI;5F!2{6_$LVbbpZoHVqz%>69Qww9XeSIa<_JrLnO9nZOsr{gZ=)W7*zi)Wy86 zy?tM^am|k9QW@FPjo6W!lt*ZOyT57w6}>;7|DtETb}GHV-eZbIrXv8Xxe1{`V8{rD zl35C*1)H)HWjlIEVfU~mAj7~(6rXWjj z2A(uEKLfje2~~et=p)BokR{}hep1oSBb@l`_Pi0+f=DW5W~XvyIP-dlY14f>ABP3E z>Uyiw?I}xd(o6mr|MVT)JlM&846t#~bs5LDdzc2^KKA!NCuKLAf(7K;fT+k$96b_}=AHa7NlN zZ`Z{4h62H$U*=oliet=}7;Y-*%uz%HUu*0t;0bbaDwG+9oXdGt8_YE9GW;2Y$lc~s z4v~<=W==BE`;7SlEsIMQvI>N|0a#>DTNycBHQoND4@^9?tS)^zGO&qh%g#`+Ii%KV z5?D!H^>a8grE(AV`K5a72M;L+d@=4mhNR_1{*6BL6~der%y{1v$kSgtytT8tbN=Ii3}+OSUl+O(Ce_%oB6my2uAJ13QEPW@R zuo%7GZ{$5@g*$tor)8vQG2owuDZk3MKR3*8iLXs;>0(e$+hFYbCNR=s&!FSd7nXdN z!7s$!n-sf5)kBye?zxb}+^s(niSkDVMY_~gIcLnwxzrW{SnyRDjim4BucnFhx%GPE zwVvro7pn=3aamOXlhIx*R~yEKJ)=67v0%i*UX((~H%-00k}dmOU{21TP_`z%oY*qo zL4)t^h|Lm$ABUW)7rXwHus3%g;}R|1mDs`qx-u(Mm*HVN#Qt2G=O-U9Rr z>7r3;tu*dSkiY|P?#4kKi0HzR1AzZN;>o>wRA+`V_k+dVsDQb@Bbw24B{L|3NzLiI zaOP|92_SkiNusQZ)WYYE$|;iQp-lrB-jaUe04X5a?wU5Q5FI$Q-YCBzs>rcK&`$)@ z@5$4ju4yPY=YO8u&V^<8w_OT`%DnRF&t4ExnZL}}6nc?JgL>#GiUSn5l9#X>o0!g;sR2Kv4M^PPaY zeE9rumh?j^MeEyVKiqZzWO`3#RJ1fc^w4PiDDaCgYFiX;yAle&2cBK4wJH%E%4hUc z5(Nqg^1(a5ZLL3P%m4Qe|4(M;`Ir26+kT+jxl|%l?u2Lh8e`mn8&&-0`&ogvzm7xk zm1laE!aJ4FCbi`J7G=tJOg>5yx8nq%)VlD{_cJF0Z_+5j+i|{#p~m^)?@iKYPMP%C ze-VB4zX3ielR+TDUL)2YPhj<+1&-GFI%C{axiP@Q?vA-Dd5wKug@*a8BZzca)Ompg zg%)Y z%On-vy(Ptt7`5|*8ytU~K^LAbq@BzwG&?HvaQ%)DS)8_8H@TDB88wg`h&>2U*^k_? zoKIF+B-Voz;3P2*hs`3Or#bAbdvoN{Q#e?ER`<3{BN$XFUIHNSRqz6}x7-gd4$ zWDsl)VwzO=BT{pR2D*ROI+uS6tkG;&e~p*Dd?mc%h`? zSBztdBtzVt54ZgK=fB_wW%il3S7_6;T^hrV2t3E`gzF-ws#Ym_|p|tbVO2mV}jRvU+fH`C_ z$%#0atnmN`Le#^i?VCw_DCD9+<3x)8jNXjR0JkJtZ886Z_=>m^}J1)@VX?%06 zAX0!zqP1}203(%?*D_aQ`a!si3=nnS_g{fH>S5lQ<`eO~+WZ4R>Z#eisJ z{P{MJEYPJBhWU&QG;xa33AZ@tC<-R$28_*g8>oqTg`J`bN!8S}8!lN?ihdv#SqL{)*Cd`--8@Aqx9X zc8+T7Hk7<2Ax=Zd$gt&(PzT-XZy<{KU##iFXtr$P?1Vh99J>%#_t@+54yDGVz&Qk# zzq8sLAr?Xm4DZ? zoX5`BmnXabq>i7&mr&@y#gpD1olcBDi-NUzyTaUwfE2R zof~$lW+JY~A*o?)0ft*oPC=0eIT%?7u#{(d`R8Gl)5E2Y_g7k4PK+nx(efXyn^W|J zNmKMdAby3w1yS&2xUmQxJ1%%-0Nq~{nb6!x3!t(Qpw4hU*<8IWbX#Lj`HgBR)jf8( z$N6M1y8Gf&w*F|S$CbX?#`)z|3FF?x!TXLZT#xhCVnbi&$#nbMSEFLio&;Hhj&tom z{WYm!!&Z7TTp^KQLxK=Uka zL9x=Iq6rWU)F+G)C0e(i!`l|Ms2_#rQG#{*=_E(V&+W(QR?VY+q_Mh<=b?UF!A6mR z`gk_n9$cs&!?@=F8ySoaI`*ClHG=%JU1Ohxo94%%Fzc06N+$sHE##7#G;*WWx-oUr zs}F_$xY%{07I6Kg&23nWigGhq-db`$a=iDy^*20(+FE~{d{S-NM{sst^oVbjM~7Ar@-MjUu4!HbwXag_UF9V^{dm_@v|P;f7=D5KXU^ zRp{tm1%ML9ph{$+?wARASu*JjtM7nB ze@UIcpr@w&c1$4K!s22~qq^wqRA`|6-po)F6~_2$W{jhBu?}i%QNg51YOK3sSZukK zimk<_ukEx#t#B>fIJWuw+zXVxNuE!?DavTv>+mv0uAHjpyNuUdkCs9qZ6Y46bqa*` zyhYD9y|Dz-;h`qWlc>}4HbSnh(3ze8|Fw3e(QLNe9#7{!6zx+(QPWFHDOxqpW9d^v z(a@SwL#Yr`#ncL|>8UANRc%c{4Gl4sM1&}+rkYBGm}jA)N(3q1JKl5FyUtqYtoO_D z#g}^}cdlId@8ACKeP8)XrkIJL!93$GlXL4UNd7LKjZmwU?{}PE7 z%a=+VSlyjE;8ECZ6A(aB!W-`apWujF9Sl|*QNVH9{5Sn6tm{-t$&ehJIR*Pf4Ut2^thu8pM*sV0H2)E zaQ7IyIc}RyNcF*Ms#1p-`LW2`_iv^ppD)j*qE9(KoLj!+)4=ECH=;4R+dj-ImSi2uw5|=nsu;{rLHnxVN5!U#+TF6d|&Bhqll~|8)1p|2@ z+M6zk48|;4^z_e_z7?zOx5%j@x`?FRcChloZ0gwLr)2qEjov{6oRZ2ZJ|*5$ZSRhR z7DppgYk_W4$vEUi1t8k1v7)^wyZpudPwYRkDUD?C1+>-AQ^5yA&@jgB`{v-M>eEBF zKIyCb>irx8PN$y{o;>)L^WElH5{EhE)TYSW(g%YKf0bT`OsnvZf9c)dObRw1tjURs zN)3ltO0mi9Z5YSso&v6b_PuuA@FkTtpZP6ZSFuA5q<6ngSH}U zjoO(wvUs9=){5Xoz1$@+%4p%L9K>=Ht#(dxOA5K><>odU?`5sLJ-0_l#zxiV1-<6r zkkd3QX#rEmFI-H&?4;ENhIoWI)eZ6XE1x6E#tdPYH6l=dF}s_LBo4XAGpKn$q(cuI zYBd~0jt3xVqpRqn;6FwP!O#JOG_DZ2rjzgCL&P8@s@K9z}T!f#g#8= zh+mr9&u?2FHEH~@giN%!Z$6!`$&;N@5;yvVY9M>Q0l{H3DZKS!FbEDQ+<&u!&;fBP z@2#%f0F+~)6=7R#jr1#{0U=~51F(D{&LbQJ!cp7ukTwMbm6NQ$A zI~(#rc*nK793Al2YOm!F@!ChxHLH9*9?K+9EQPYAP5}aZSS+kg)mNS5vO41MnoA;h z-={>-4mh}Gq0-H0#)xmwu58PoDc8Jo?~Zq96G!93yQ)%=nfhsn&4;l`&2Q_q8b`== zfmMj@)~GVB{6_HcF3;kcqO&%VwB=sx(ptnEEy2eA<_@q&YPRy$;*OpG73dk1fsHVu zHDuCd`)k|Ft8N;9^RtidLFuU37sCz#n8vhY%}HRplBhrfcC7~ zwQz5o?3w3Zmg=zk0T?=$`S(4A<;miSm+#l@atoTHWnvSnRtXtQ9dv}fdYI0_SVbR3 zWf!p>KFDn9(7B`j=Zk3Oa+D!Qlhf}crH;0}m^if!?RQ%{b&;=VZ>PxV)KD~StEFr< zje5})n4lHL3bBDH?w<5L(fl$lNxM5!Mb4dh%gt14Uw^#^av#4NOO7M#Dar1ywI}=r z>9(8P$I)yi2In#)qwlS~0iWnkZ-JU)Zm0G=3i-hP$ClC>ZtK2tzJuV^jh_=)Wi#Jx z9hcX=?D-KGdS|R?Z6Z+wZPH0+0*^>hRr5bvw29Ps_?;(8OoTc_FxA~IDpad$tbco2;}%T3!XHWzn!1(#uc3Zjn!|#zUgomqMij3 zch}u&`yVeu&%c-aA%I9@Kd01@mh(ksf>WQ*sxXdcj-`{{05AkMh>A=Q#0!j~BeL}^ zz;-3ldvzn?)6SxO2zOU_;kGxFJ8F%kp}lvSlT zSRe)bKfTO>hmHhk6di=#y$0~0{=W#0`;3_)Ajf~Pvj+6G-{6qUR1Gg?QbuTXxE|P( zwrp8|G=Y!7%KIWUy}UJ{@)y;Xh*(>LpP4bi->NjCL@UJyEin0`tm`5jSm`k;()02# zjH?tUL{Jd~iE0VSfFBa+&)~onY+PCZ$hTJlq&1q)47$IzOk{=08*)Vtt4kdaBPN2^ zrX2VbIf+vWf=WNoq9G0*7Q)5BNEISt%m#D+)l<{J=7xD&uhox5E35GoOXaZPkkQ{I zN@W!P8PccHy1GKrYSI~Us^>ePb&q2|!(^mrJ7{wBK$B1(^ns(n+mJ%P=oQkvnjx%H zbbvpJ+FtTS91Jk6XeHF~-rXFnzHXcGb8!$&Hs-L^Se6wJ0t_Xo>bVH%fV}-EQN_VOf-=ot=WB`45BN@sx-$1f;`Fb% zkh621j{Db7uuz8tv%Y(2C+d?g+yD!o1UAfv%t)`j{=I(-T zw9#7DSBrsLs)yRtE*hxG+VW+723WoKN|)oo&W`e8tP$-147B2(#Dc4$di&?{i%<^%5`np5eXVE_`qCLH@lY7hTL?{ z;1$anH+cDb(WQ*R`mFH`2UJm8xbDYAF9O*~MPikBMPgHO!7fPG&)R6fVhuvwYO-|& zN`j#QIVT$rtVRzuU9V`7&?U^ea^&%H>gO8~ReEa&D0ao!^3?_Ygw(aBV&_?hdnm?( zh@Y8FO^-p`Ie{$&eB;`q*!{39+3lW1SPQ!03Mp>g=FG^Mq-1Q!(s_rw{p&5bkYOO} zDZ3P%e7j2No_U*XO{U4cRM_ccS7^9D%+L9=F?DLucro#I6|Nj#ZC{Kv0mcBu6m!nLmRjxiwtttpmQ7+wVJ_QVaI z_-vgWM8JU)gJi4jB)l>oVpOr#4;*GGBY8@4hfzCUo}twVv(9a%5BT^uJ&%A1^kgN+ zzB(o)1c%r!$Pt%(0U0psAmS1lpIHBT9=)x64pZZD=d-E@ZTt0KK8_c6to)wheL5XR zBOc^<8BOoFrs@!P-tcKh4u&lgwQ7vug>UZv?yl+Z>&Fp7B!VhUMu+ylA`<|)+GA-@ z`w^XUQI``sY>Zn}Au(^|9(}9s5w}wg_;WRV!};UdVln8t07}?P^?j8vn9p;VzNM7` zTg;ZFMLQbc!5D5#qTM!KgdjHU&&PpQ0u>nX3<=U8R5{bc%8s(UJGt2u%Wv;0rBr!0 zYgP#=1UBCqmv%+!x8J@>j2bZEG{(9u-w4-RwN_3f#YXQ5k>XuS*v|ylv5Oq*(I!LD z%#ro4Z`!nLTJNAPj3Z?m9cH)S`?$hkuJl#YkL`okYw%gE8eKcm{(!?B#_}3if4ZFwFsIK=p2L#L&u@7jt0zER(JVbo7xik0i z$xL#65BqyEo&{e&2xy^M#Z3Rr3V$GD3e$|AL|_!*hJ@;V#d8@AI>K!X^|qXrsR>ws}AqdIQ1S3hn8>!6%uXu}!^C(}rB)1cA)C zfT6dbx%SN|!yO>F{t&V4;WD>d!4wwamnF6TNHX_U3hcAx+~P7XL|#;TO_$(Q*ArIz zA#MAtSUVXlefQ@GWdr=~!+wf^m$3w{0Nu9IlKLbo#3Dya{b5e^ehBKL?ysIFe8a;? zGi;OI*|53R1)#<1+n(2eo3dRm@STrs8Ln7yse*4Fm6Dae=%dY99B}99?L+Pg+6g4Z z6KkT;CAc`P3)b1^OTPMaSv%*LOx{-M#I@cr;U{U7$^kr#H)a*+lBF)(1*6=bZKbKG z?=)x*8TXrA{S=rg=(D8X65ieA`ReVXh)+XTPh%f1cBJET|fVMN{O%cdZuZ(_>J|hHk|lo-U$$D zgJs*K7zn5rXP9>~2kRx(=2X?KtEGNg%NvRC?xxODr%g%O=7YiBp#`l$cR%LC5upp1 zpdZ^M9g7x;F;DHj`r0!Kgwy)x^A$HOaoa>=#x3Ugai2i^l0pKJsE)@3c7dc2&^8pi z7%{kXEv>k`E~QeX*7MifrC$a&Hmx@?mxRZ0qmHKQZBk(8k~{^c)?r@Ra`qb(700ty zBy9bZJKo19;<{q#7!cZb4%b%L%^ou_fzDPv@nrQ6M%8`ojPOo(tySO&$`~`}2_Qoo zKPZXs!#(<$)|+jo{z%!&Oc+2|p)0VYWp3T?dx-L6es34)28%v^EDZ-tA3TSK~w+^aR7a_iiGL$NR<7lAf+ z6~&~0wy_+bWBhA0;Qkr%nufJ%U&p7j9Kfl-(gUZcZbDH`mKv%vvd4zAttuJo@;9!i zorIW!hR4LZ@`F%)hn6--R9>ADFGkiAlNAGU0K2q9k^(3x^sp+!!m9slqy5{PD`a&; zWZtWBe?c#!d6Av8258JywkONfFy{B_9p$MX&uHuag;gV!vE&~tI@+2|waz|hei_xI zU_Z7YSC1OH$wBDsp+@+0DufY-^5Gxs-Jkp%epV#%$+D$wbczviEwpi~sB*p7EJt1b zAs&FJ$`&6&M%11rc@;pm7xDxBWPlbysKZvs6mgx?M@?dqm^q=}u)m=g!rJ4UY#2vU zTPbbSdiAAf#RgWZVnX?N?v&$g<@<1W68lP7qi3}}HW)6#*mh(Ljih^A#d>KgwP<;) z2mAh2M#i)ZjdGFYYSeDfSg#`h90v0e_ZyPtJWkE`1TLBWicC1gxkn(T8at;9T?R&I@qo1<@PXvzxBxoPk z{AHG-d<3waYCo5Yb8AC2$fs7mW2c59RVNQV-p6(+m>ujP8m zs%q&b13Vdx3 z;(iAtG3&N7yi(JWq9!9gDgjFx%e18cR%k(~q`iPLCbHi?km=2!FT6`g1*f44FKRt~ zS~02Q2|<2=Adh|c$=yxbA~6gtH<+^23W2QdN_VzA39c;%ZhP*U-9B@gAs&|mhlEe<0Eu$P zJojbyQSpGEsID<`&c2IL=iYmx(YROdoGfB4Svesu&vtBvvEJ!57prHw{Zod=1T?`X zXMR`4XK&taWikTAvJcGWtN`z`jGX8Gw@rJ|L8D81vD=;C{wa!|Py(!HY&~dlk1=d* zK_2l+M-WX`<$z>1p3=TMetpz05q&%;$mx#Q|fKT-l4)jJ2Tdx!LlNS>*&oqS{= zG`#rSKtx#j%;44Ud4G5);=6zQn!zfQ<$IqNNn7#!Eih9reMYS2+vZ2yFe{LOK_A72 z-(S6XBp>@}^%=cmwb-H{LQ4ROAa~aBGG{#SJ%B@)qda-I80r6;6jr$8XD0#4KQtUV zF^~swQ%2%~`E?{TM40cp6-G+)WibC!D8IESaPuZV1V!o_ANHu4h&g!aIoKl?1Kwfh zTr}{*tPKikO=8vJ4*&j-y28H&@&B<{@gH6^!{;PO_naMLeD)-$H5pKNkkqLI5jwbL U5F$}2!n)gAM&^cP`VXG}6VZTD$^ZZW literal 0 HcmV?d00001 diff --git a/docs/guides/qdrant/monitoring/_index.md b/docs/guides/qdrant/monitoring/_index.md index ab29e3dc2..a7545f31d 100644 --- a/docs/guides/qdrant/monitoring/_index.md +++ b/docs/guides/qdrant/monitoring/_index.md @@ -5,6 +5,6 @@ menu: identifier: qdrant-monitoring name: Monitoring parent: qdrant-guides - weight: 20 + weight: 140 menu_name: docs_{{ .version }} --- diff --git a/docs/guides/qdrant/ops-request/_index.md b/docs/guides/qdrant/ops-request/_index.md deleted file mode 100644 index 3a64a4ebe..000000000 --- a/docs/guides/qdrant/ops-request/_index.md +++ /dev/null @@ -1,10 +0,0 @@ ---- -title: Qdrant Ops Request -menu: - docs_{{ .version }}: - identifier: qdrant-ops-request - name: Ops Request - parent: qdrant-guides - weight: 35 -menu_name: docs_{{ .version }} ---- diff --git a/docs/guides/qdrant/ops-request/overview.md b/docs/guides/qdrant/ops-request/overview.md deleted file mode 100644 index 62ec04e59..000000000 --- a/docs/guides/qdrant/ops-request/overview.md +++ /dev/null @@ -1,51 +0,0 @@ ---- -title: Qdrant Ops Request Overview -menu: - docs_{{ .version }}: - identifier: qdrant-ops-request-overview - name: Overview - parent: qdrant-ops-request - weight: 10 -menu_name: docs_{{ .version }} -section_menu_id: guides ---- - -> New to KubeDB? Please start [here](/docs/README.md). - -# Qdrant Day-2 Operations - -This guide provides an overview of the day-2 operational workflows that KubeDB supports for `Qdrant` databases via the `QdrantOpsRequest` CRD. - -## Before You Begin - -- You should be familiar with the following `KubeDB` concepts: - - [Qdrant](/docs/guides/qdrant/concepts/qdrant.md) - - [QdrantOpsRequest](/docs/guides/qdrant/concepts/opsrequest.md) - -## Supported Operations - -KubeDB supports the following day-2 operations for Qdrant: - -| Operation | Description | -|-----------|-------------| -| [UpdateVersion](/docs/guides/qdrant/update-version/overview.md) | Update the version of a running Qdrant database | -| [HorizontalScaling](/docs/guides/qdrant/scaling/horizontal-scaling/overview.md) | Scale the number of Qdrant nodes up or down | -| [VerticalScaling](/docs/guides/qdrant/scaling/vertical-scaling/overview.md) | Update CPU and memory resources of Qdrant nodes | -| [VolumeExpansion](/docs/guides/qdrant/volume-expansion/overview.md) | Expand the persistent volume claim size of Qdrant nodes | -| [Reconfigure](/docs/guides/qdrant/reconfigure/overview.md) | Reconfigure a running Qdrant database with new configuration | -| [ReconfigureTLS](/docs/guides/qdrant/reconfigure-tls/overview.md) | Add, rotate, or remove TLS certificates for Qdrant | -| [Restart](/docs/guides/qdrant/restart/restart.md) | Restart the Qdrant database pods in a rolling fashion | -| [RotateAuth](/docs/guides/qdrant/rotate-auth/overview.md) | Rotate the authentication credentials of a Qdrant database | - -## How Ops Requests Work - -All day-2 operations for Qdrant are performed through the `QdrantOpsRequest` CRD. The general workflow is: - -1. The user creates a `QdrantOpsRequest` CR with the desired operation type and parameters. -2. `KubeDB-ops-manager` operator watches for `QdrantOpsRequest` CRs. -3. When it finds one, it pauses the `Qdrant` object to prevent conflicting operations. -4. The operator performs the requested operation (e.g., updates images, scales nodes, expands volumes). -5. After the operation completes successfully, the operator updates the `Qdrant` object and resumes it. -6. The `QdrantOpsRequest` status transitions to `Successful`. - -> **Note:** Only one `QdrantOpsRequest` should be active at a time for a given `Qdrant` database. Wait for one operation to complete before starting another. diff --git a/docs/guides/qdrant/private-registry/_index.md b/docs/guides/qdrant/private-registry/_index.md deleted file mode 100644 index b8928dd38..000000000 --- a/docs/guides/qdrant/private-registry/_index.md +++ /dev/null @@ -1,10 +0,0 @@ ---- -title: Run Qdrant using Private Registry -menu: - docs_{{ .version }}: - identifier: qdrant-private-registry - name: Private Registry - parent: qdrant-guides - weight: 120 -menu_name: docs_{{ .version }} ---- \ No newline at end of file diff --git a/docs/guides/qdrant/private-registry/using-private-registry.md b/docs/guides/qdrant/private-registry/using-private-registry.md deleted file mode 100644 index a2c8b7120..000000000 --- a/docs/guides/qdrant/private-registry/using-private-registry.md +++ /dev/null @@ -1,145 +0,0 @@ ---- -title: Run Qdrant using Private Registry -menu: - docs_{{ .version }}: - identifier: qdrant-using-private-registry - name: Quickstart - parent: qdrant-private-registry - weight: 10 -menu_name: docs_{{ .version }} -section_menu_id: guides ---- - -> New to KubeDB? Please start [here](/docs/README.md). - -# Using Private Docker Registry - -KubeDB supports using private Docker registry. This tutorial will show you how to run KubeDB managed Qdrant database using private Docker images. - -## Before You Begin - -- At first, you need to have a Kubernetes cluster, and the `kubectl` command-line tool must be configured to communicate with your cluster. If you do not already have a cluster, you can create one by using [kind](https://kind.sigs.k8s.io/docs/user/quick-start/). - -- To keep things isolated, this tutorial uses a separate namespace called `demo` throughout this tutorial. - -```bash -$ kubectl create ns demo -namespace/demo created -``` - -> **Note:** YAML files used in this tutorial are stored in [docs/examples/qdrant/private-registry](https://github.com/kubedb/docs/tree/{{< param "info.version" >}}/docs/examples/qdrant/private-registry) folder in GitHub repository [kubedb/docs](https://github.com/kubedb/docs). - -## Prepare Private Docker Registry - -You will need a Docker private [registry](https://docs.docker.com/registry/) or [private repository](https://docs.docker.com/docker-hub/repos/#private-repositories). In this tutorial we will use a private repository on [Docker Hub](https://hub.docker.com/). - -You need to push the required images from KubeDB's [Docker hub account](https://hub.docker.com/r/qdrant/) into your private registry. For Qdrant, push `DB_IMAGE` of the following `QdrantVersion`s, where `deprecated` is not true, to your private registry. - -```bash -$ kubectl get qdrantversions -o=custom-columns=NAME:.metadata.name,VERSION:.spec.version,DB_IMAGE:.spec.db.image,DEPRECATED:.spec.deprecated -NAME VERSION DB_IMAGE DEPRECATED -1.7.4 1.7.4 qdrant/qdrant:v1.7.4 -1.10.0 1.10.0 qdrant/qdrant:v1.10.0 -1.14.0 1.14.0 qdrant/qdrant:v1.14.0 -1.17.0 1.17.0 qdrant/qdrant:v1.17.0 -``` - -Docker hub repository: -- [qdrant/qdrant](https://hub.docker.com/r/qdrant/qdrant) - -## Create ImagePullSecret - -`ImagePullSecrets` is a type of Kubernetes Secret whose purpose is to pull private images from a Docker registry. It allows you to specify the URL of the Docker registry, credentials for logging in, and the image name of your private Docker image. - -Run the following command, substituting the appropriate uppercase values, to create an image pull secret for your private Docker registry: - -```bash -$ kubectl create secret docker-registry -n demo myregistrykey \ - --docker-server=DOCKER_REGISTRY_SERVER \ - --docker-username=DOCKER_USER \ - --docker-email=DOCKER_EMAIL \ - --docker-password=DOCKER_PASSWORD -secret/myregistrykey created -``` - -If you wish to follow other ways to pull private images see [official docs](https://kubernetes.io/docs/concepts/containers/images/) of Kubernetes. - -## Install KubeDB operator - -When installing KubeDB operator, set the flags `--docker-registry` and `--image-pull-secret` to the appropriate values. Follow the steps to [install KubeDB operator](/docs/setup/README.md) properly in your cluster so that it points to the `DOCKER_REGISTRY` you wish to pull images from. - -## Create QdrantVersion CRD - -KubeDB uses images specified in `QdrantVersion` CRD for the database. You have to create a `QdrantVersion` CRD specifying images from your private registry. Then, you have to point this `QdrantVersion` CRD in `spec.version` field of the `Qdrant` object. For more details about `QdrantVersion` CRD, please visit [here](/docs/guides/qdrant/concepts/catalog.md). - -Here is an example of a `QdrantVersion` CRD. Replace `PRIVATE_REGISTRY` with your private registry: - -```yaml -apiVersion: catalog.kubedb.com/v1alpha1 -kind: QdrantVersion -metadata: - name: "1.17.0-private" -spec: - db: - image: PRIVATE_REGISTRY/qdrant:v1.17.0 - version: "1.17.0" -``` - -```bash -$ kubectl apply -f https://github.com/kubedb/docs/raw/{{< param "info.version" >}}/docs/examples/qdrant/private-registry/qdrantversion.yaml -qdrantversion.catalog.kubedb.com/1.17.0-private created -``` - -## Deploy Qdrant from Private Registry - -While deploying `Qdrant` from private registry, you have to add `myregistrykey` secret in `spec.podTemplate.spec.imagePullSecrets` and specify `1.17.0-private` in `spec.version` field. - -Below is the YAML for Qdrant crd we are going to create: - -```yaml -apiVersion: kubedb.com/v1alpha2 -kind: Qdrant -metadata: - name: pvt-reg-qdrant - namespace: demo -spec: - version: "1.17.0-private" - replicas: 3 - storage: - storageClassName: "standard" - accessModes: - - ReadWriteOnce - resources: - requests: - storage: 1Gi - podTemplate: - spec: - imagePullSecrets: - - name: myregistrykey - deletionPolicy: WipeOut -``` - -Now run the command to create this Qdrant object: - -```bash -$ kubectl apply -f https://github.com/kubedb/docs/raw/{{< param "info.version" >}}/docs/examples/qdrant/private-registry/pvt-reg-qdrant.yaml -qdrant.kubedb.com/pvt-reg-qdrant created -``` - -To check if the images pulled successfully from the registry, wait for the Qdrant to go into `Ready` state: - -```bash -$ kubectl get qdrant -n demo pvt-reg-qdrant -w -NAME VERSION STATUS AGE -pvt-reg-qdrant 1.17.0-private Provisioning 5s -pvt-reg-qdrant 1.17.0-private Ready 1m -``` - -## Cleaning up - -To clean up the Kubernetes resources created by this tutorial, run: - -```bash -kubectl delete qdrant -n demo pvt-reg-qdrant -kubectl delete ns demo -``` \ No newline at end of file diff --git a/docs/guides/qdrant/quickstart/_index.md b/docs/guides/qdrant/quickstart/_index.md index 5a0211fd2..dfb60004b 100644 --- a/docs/guides/qdrant/quickstart/_index.md +++ b/docs/guides/qdrant/quickstart/_index.md @@ -5,6 +5,6 @@ menu: identifier: qdrant-quickstart name: Quickstart parent: qdrant-guides - weight: 15 + weight: 10 menu_name: docs_{{ .version }} --- \ No newline at end of file diff --git a/docs/guides/qdrant/quickstart/rbac.md b/docs/guides/qdrant/quickstart/rbac.md deleted file mode 100644 index 6e24e415a..000000000 --- a/docs/guides/qdrant/quickstart/rbac.md +++ /dev/null @@ -1,216 +0,0 @@ ---- -title: Qdrant RBAC -menu: - docs_{{ .version }}: - identifier: qdrant-quickstart-rbac - name: RBAC - parent: qdrant-quickstart - weight: 20 -menu_name: docs_{{ .version }} -section_menu_id: guides ---- - -> New to KubeDB? Please start [here](/docs/README.md). - -# RBAC Permissions for Qdrant - -If RBAC is enabled in clusters, some Qdrant-specific RBAC permissions are required. These permissions are required for the KubeDB operator to manage Qdrant pods properly. - -Here is the list of additional permissions required by the StatefulSet of Qdrant: - -| Kubernetes Resource | Resource Names | Permission required | -|---------------------|-----------------|------------------------| -| statefulsets | `{qdrant-name}` | get | -| pods | | list, patch | -| pods/exec | | create | -| qdrants | | get | -| configmaps | `{qdrant-name}` | get, update, create | -| secrets | | get, list | - -## Before You Begin - -At first, you need to have a Kubernetes cluster, and the `kubectl` command-line tool must be configured to communicate with your cluster. If you do not already have a cluster, you can create one by using [kind](https://kind.sigs.k8s.io/docs/user/quick-start/). - -Now, install KubeDB cli on your workstation and KubeDB operator in your cluster following the steps [here](/docs/setup/README.md). - -To keep things isolated, this tutorial uses a separate namespace called `demo` throughout this tutorial. - -```bash -$ kubectl create ns demo -namespace/demo created -``` - -> **Note:** YAML files used in this tutorial are stored in [docs/examples/qdrant/quickstart](https://github.com/kubedb/docs/tree/{{< param "info.version" >}}/docs/examples/qdrant/quickstart) folder in GitHub repository [kubedb/docs](https://github.com/kubedb/docs). - -## Create a Qdrant Database - -Below is the `Qdrant` object created in this tutorial: - -```yaml -apiVersion: kubedb.com/v1alpha2 -kind: Qdrant -metadata: - name: qdrant-rbac - namespace: demo -spec: - version: "1.17.0" - replicas: 3 - storage: - storageClassName: "standard" - accessModes: - - ReadWriteOnce - resources: - requests: - storage: 1Gi - deletionPolicy: Delete -``` - -```bash -$ kubectl create -f https://github.com/kubedb/docs/raw/{{< param "info.version" >}}/docs/examples/qdrant/quickstart/qdrant-rbac.yaml -qdrant.kubedb.com/qdrant-rbac created -``` - -When this `Qdrant` object is created, KubeDB operator creates a Role, ServiceAccount, and RoleBinding with the matching Qdrant name and uses that ServiceAccount in the corresponding StatefulSet. - -Let's see what KubeDB operator has created for additional RBAC permissions. - -### Role - -KubeDB operator creates a Role object `qdrant-rbac` in the same namespace as the Qdrant object: - -```yaml -$ kubectl get role -n demo qdrant-rbac -o yaml -apiVersion: rbac.authorization.k8s.io/v1 -kind: Role -metadata: - labels: - app.kubernetes.io/component: database - app.kubernetes.io/instance: qdrant-rbac - app.kubernetes.io/managed-by: kubedb.com - app.kubernetes.io/name: qdrants.kubedb.com - name: qdrant-rbac - namespace: demo - ownerReferences: - - apiVersion: kubedb.com/v1alpha2 - blockOwnerDeletion: true - controller: true - kind: Qdrant - name: qdrant-rbac -rules: -- apiGroups: - - apps - resourceNames: - - qdrant-rbac - resources: - - statefulsets - verbs: - - get -- apiGroups: - - kubedb.com - resourceNames: - - qdrant-rbac - resources: - - qdrants - verbs: - - get -- apiGroups: - - "" - resources: - - pods - verbs: - - get - - list - - patch - - delete -- apiGroups: - - "" - resources: - - pods/exec - verbs: - - create -- apiGroups: - - "" - resources: - - secrets - verbs: - - get - - list -- apiGroups: - - "" - resources: - - configmaps - verbs: - - create - - get - - update -``` - -### ServiceAccount - -KubeDB operator creates a ServiceAccount object `qdrant-rbac` in the same namespace as the Qdrant object: - -```yaml -$ kubectl get serviceaccount -n demo qdrant-rbac -o yaml -apiVersion: v1 -kind: ServiceAccount -metadata: - labels: - app.kubernetes.io/component: database - app.kubernetes.io/instance: qdrant-rbac - app.kubernetes.io/managed-by: kubedb.com - app.kubernetes.io/name: qdrants.kubedb.com - name: qdrant-rbac - namespace: demo - ownerReferences: - - apiVersion: kubedb.com/v1alpha2 - blockOwnerDeletion: true - controller: true - kind: Qdrant - name: qdrant-rbac -``` - -This ServiceAccount is used in the StatefulSet created for the Qdrant object. - -### RoleBinding - -KubeDB operator creates a RoleBinding object `qdrant-rbac` in the same namespace as the Qdrant object: - -```yaml -$ kubectl get rolebinding -n demo qdrant-rbac -o yaml -apiVersion: rbac.authorization.k8s.io/v1 -kind: RoleBinding -metadata: - labels: - app.kubernetes.io/component: database - app.kubernetes.io/instance: qdrant-rbac - app.kubernetes.io/managed-by: kubedb.com - app.kubernetes.io/name: qdrants.kubedb.com - name: qdrant-rbac - namespace: demo - ownerReferences: - - apiVersion: kubedb.com/v1alpha2 - blockOwnerDeletion: true - controller: true - kind: Qdrant - name: qdrant-rbac -roleRef: - apiGroup: rbac.authorization.k8s.io - kind: Role - name: qdrant-rbac -subjects: -- kind: ServiceAccount - name: qdrant-rbac - namespace: demo -``` - -This object binds Role `qdrant-rbac` with ServiceAccount `qdrant-rbac`. - -## Cleaning up - -To clean up the Kubernetes resources created by this tutorial, run: - -```bash -kubectl patch -n demo qdrant/qdrant-rbac -p '{"spec":{"deletionPolicy":"WipeOut"}}' --type="merge" -kubectl delete -n demo qdrant/qdrant-rbac -kubectl delete ns demo -``` \ No newline at end of file diff --git a/docs/guides/qdrant/reconfigure-tls/_index.md b/docs/guides/qdrant/reconfigure-tls/_index.md index 75e1236e8..8c870befc 100644 --- a/docs/guides/qdrant/reconfigure-tls/_index.md +++ b/docs/guides/qdrant/reconfigure-tls/_index.md @@ -5,6 +5,6 @@ menu: identifier: qdrant-reconfigure-tls name: Reconfigure TLS parent: qdrant-guides - weight: 33 + weight: 70 menu_name: docs_{{ .version }} --- diff --git a/docs/guides/qdrant/reconfigure/_index.md b/docs/guides/qdrant/reconfigure/_index.md index fe61847fb..0e41ac5f6 100644 --- a/docs/guides/qdrant/reconfigure/_index.md +++ b/docs/guides/qdrant/reconfigure/_index.md @@ -5,6 +5,6 @@ menu: identifier: qdrant-reconfigure name: Reconfigure parent: qdrant-guides - weight: 32 + weight: 60 menu_name: docs_{{ .version }} --- diff --git a/docs/guides/qdrant/restart/_index.md b/docs/guides/qdrant/restart/_index.md index 6c3b1e209..81fb1f1f8 100644 --- a/docs/guides/qdrant/restart/_index.md +++ b/docs/guides/qdrant/restart/_index.md @@ -5,6 +5,6 @@ menu: identifier: qdrant-restart name: Restart parent: qdrant-guides - weight: 31 + weight: 80 menu_name: docs_{{ .version }} --- diff --git a/docs/guides/qdrant/rotate-auth/_index.md b/docs/guides/qdrant/rotate-auth/_index.md index 2ee5055d5..53839e13c 100644 --- a/docs/guides/qdrant/rotate-auth/_index.md +++ b/docs/guides/qdrant/rotate-auth/_index.md @@ -5,6 +5,6 @@ menu: identifier: qdrant-rotate-auth name: Rotate Auth parent: qdrant-guides - weight: 34 + weight: 90 menu_name: docs_{{ .version }} --- diff --git a/docs/guides/qdrant/scaling/_index.md b/docs/guides/qdrant/scaling/_index.md index d9281f724..70ca1f624 100644 --- a/docs/guides/qdrant/scaling/_index.md +++ b/docs/guides/qdrant/scaling/_index.md @@ -5,6 +5,6 @@ menu: identifier: qdrant-scaling name: Scaling parent: qdrant-guides - weight: 37 + weight: 120 menu_name: docs_{{ .version }} --- diff --git a/docs/guides/qdrant/tls/_index.md b/docs/guides/qdrant/tls/_index.md index 2f261059d..077ea4ee2 100644 --- a/docs/guides/qdrant/tls/_index.md +++ b/docs/guides/qdrant/tls/_index.md @@ -5,6 +5,6 @@ menu: identifier: qdrant-tls name: TLS parent: qdrant-guides - weight: 25 + weight: 40 menu_name: docs_{{ .version }} --- diff --git a/docs/guides/qdrant/update-version/_index.md b/docs/guides/qdrant/update-version/_index.md index 1baeebb52..b2fe5a572 100644 --- a/docs/guides/qdrant/update-version/_index.md +++ b/docs/guides/qdrant/update-version/_index.md @@ -5,6 +5,6 @@ menu: identifier: qdrant-update-version name: Update Version parent: qdrant-guides - weight: 35 + weight: 100 menu_name: docs_{{ .version }} --- diff --git a/docs/guides/qdrant/volume-expansion/_index.md b/docs/guides/qdrant/volume-expansion/_index.md index b659e4426..191ed04e0 100644 --- a/docs/guides/qdrant/volume-expansion/_index.md +++ b/docs/guides/qdrant/volume-expansion/_index.md @@ -5,6 +5,6 @@ menu: identifier: qdrant-volume-expansion name: Volume Expansion parent: qdrant-guides - weight: 36 + weight: 110 menu_name: docs_{{ .version }} --- From d2b876e041810de988d704a1f658c1d4bc007250 Mon Sep 17 00:00:00 2001 From: Abdullah Al Masood <98589987+al-masood@users.noreply.github.com> Date: Mon, 4 May 2026 11:48:42 +0600 Subject: [PATCH 04/10] distributed deployment and backup Signed-off-by: Abdullah Al Masood <98589987+al-masood@users.noreply.github.com> --- .../qdrant/backup/logical/aws-secret.yaml | 9 + .../backup/logical/backup-configuration.yaml | 39 ++ .../qdrant/backup/logical/backup-storage.yaml | 20 + .../backup/logical/encryption-secret.yaml | 8 + .../examples/qdrant/backup/logical/minio.yaml | 78 +++ .../qdrant/backup/logical/qdrant-addon.yaml | 116 ++++ .../logical/qdrant-backup-function.yaml | 14 + .../logical/qdrant-restore-function.yaml | 15 + .../backup/logical/retention-policy.yaml | 15 + .../volumesnapshot/backup-configuration.yaml | 33 ++ .../backup/volumesnapshot/backup-storage.yaml | 20 + .../volumesnapshot/encryption-secret.yaml | 8 + .../backup/volumesnapshot/qdrant-addon.yaml | 21 + .../qdrant-csi-snapshotter-function.yaml | 11 + .../backup/volumesnapshot/qdrant-restore.yaml | 17 + .../volumesnapshot/restore-session.yaml | 21 + .../volumesnapshot/volumesnapshotclass.yaml | 8 + .../quickstart/yamls/qdrant-distributed.yaml | 28 + .../quickstart/yamls/qdrant-hor-scaling.yaml | 11 + docs/guides/qdrant/autoscaler/_index.md | 2 +- docs/guides/qdrant/backup/_index.md | 4 +- docs/guides/qdrant/backup/logical/index.md | 526 ++++++++++++++++++ docs/guides/qdrant/backup/overview.md | 58 -- .../qdrant/backup/volumesnapshot/index.md | 478 ++++++++++++++++ docs/guides/qdrant/configuration/_index.md | 2 +- .../qdrant/distributed-deployment/_index.md | 10 + .../qdrant/distributed-deployment/overview.md | 187 +++++++ docs/guides/qdrant/monitoring/_index.md | 2 +- docs/guides/qdrant/quickstart/quickstart.md | 18 +- docs/guides/qdrant/reconfigure-tls/_index.md | 2 +- docs/guides/qdrant/reconfigure/_index.md | 2 +- docs/guides/qdrant/restart/_index.md | 2 +- docs/guides/qdrant/rotate-auth/_index.md | 2 +- docs/guides/qdrant/scaling/_index.md | 2 +- docs/guides/qdrant/tls/_index.md | 2 +- docs/guides/qdrant/update-version/_index.md | 2 +- docs/guides/qdrant/volume-expansion/_index.md | 2 +- 37 files changed, 1712 insertions(+), 83 deletions(-) create mode 100644 docs/examples/qdrant/backup/logical/aws-secret.yaml create mode 100644 docs/examples/qdrant/backup/logical/backup-configuration.yaml create mode 100644 docs/examples/qdrant/backup/logical/backup-storage.yaml create mode 100644 docs/examples/qdrant/backup/logical/encryption-secret.yaml create mode 100644 docs/examples/qdrant/backup/logical/minio.yaml create mode 100644 docs/examples/qdrant/backup/logical/qdrant-addon.yaml create mode 100644 docs/examples/qdrant/backup/logical/qdrant-backup-function.yaml create mode 100644 docs/examples/qdrant/backup/logical/qdrant-restore-function.yaml create mode 100644 docs/examples/qdrant/backup/logical/retention-policy.yaml create mode 100644 docs/examples/qdrant/backup/volumesnapshot/backup-configuration.yaml create mode 100644 docs/examples/qdrant/backup/volumesnapshot/backup-storage.yaml create mode 100644 docs/examples/qdrant/backup/volumesnapshot/encryption-secret.yaml create mode 100644 docs/examples/qdrant/backup/volumesnapshot/qdrant-addon.yaml create mode 100644 docs/examples/qdrant/backup/volumesnapshot/qdrant-csi-snapshotter-function.yaml create mode 100644 docs/examples/qdrant/backup/volumesnapshot/qdrant-restore.yaml create mode 100644 docs/examples/qdrant/backup/volumesnapshot/restore-session.yaml create mode 100644 docs/examples/qdrant/backup/volumesnapshot/volumesnapshotclass.yaml create mode 100644 docs/examples/qdrant/quickstart/yamls/qdrant-distributed.yaml create mode 100644 docs/examples/qdrant/quickstart/yamls/qdrant-hor-scaling.yaml create mode 100644 docs/guides/qdrant/backup/logical/index.md delete mode 100644 docs/guides/qdrant/backup/overview.md create mode 100644 docs/guides/qdrant/backup/volumesnapshot/index.md create mode 100644 docs/guides/qdrant/distributed-deployment/_index.md create mode 100644 docs/guides/qdrant/distributed-deployment/overview.md diff --git a/docs/examples/qdrant/backup/logical/aws-secret.yaml b/docs/examples/qdrant/backup/logical/aws-secret.yaml new file mode 100644 index 000000000..15100ad5d --- /dev/null +++ b/docs/examples/qdrant/backup/logical/aws-secret.yaml @@ -0,0 +1,9 @@ +apiVersion: v1 +kind: Secret +metadata: + name: aws-secret + namespace: default +type: Opaque +stringData: + AWS_ACCESS_KEY_ID: minioadmin + AWS_SECRET_ACCESS_KEY: minioadmin \ No newline at end of file diff --git a/docs/examples/qdrant/backup/logical/backup-configuration.yaml b/docs/examples/qdrant/backup/logical/backup-configuration.yaml new file mode 100644 index 000000000..dccda2de1 --- /dev/null +++ b/docs/examples/qdrant/backup/logical/backup-configuration.yaml @@ -0,0 +1,39 @@ +apiVersion: core.kubestash.com/v1alpha1 +kind: BackupConfiguration +metadata: + name: sample-qdrant-backup + namespace: default +spec: + target: + apiGroup: kubedb.com + kind: Qdrant + namespace: default + name: qdrant-sample + backends: + - name: minio-backend + storageRef: + namespace: default + name: minio-storage + retentionPolicy: + name: demo-retention + namespace: default + sessions: + - name: frequent-backup + scheduler: + schedule: "*/5 * * * *" + jobTemplate: + backoffLimit: 1 + repositories: + - name: minio-qdrant-repo + backend: minio-backend + directory: /qdrant + encryptionSecret: + name: encrypt-secret + namespace: default + addon: + name: qdrant-addon + tasks: + - name: logical-backup + params: + collections: "my_collection" + diff --git a/docs/examples/qdrant/backup/logical/backup-storage.yaml b/docs/examples/qdrant/backup/logical/backup-storage.yaml new file mode 100644 index 000000000..53bef0c25 --- /dev/null +++ b/docs/examples/qdrant/backup/logical/backup-storage.yaml @@ -0,0 +1,20 @@ +apiVersion: storage.kubestash.com/v1alpha1 +kind: BackupStorage +metadata: + name: minio-storage + namespace: default +spec: + storage: + provider: s3 + s3: + bucket: qdrant-backups + endpoint: http://minio.default.svc:9000 + insecureTLS: true + prefix: backup/demo + region: us-east-1 + secretName: aws-secret + usagePolicy: + allowedNamespaces: + from: All + default: true + deletionPolicy: Delete \ No newline at end of file diff --git a/docs/examples/qdrant/backup/logical/encryption-secret.yaml b/docs/examples/qdrant/backup/logical/encryption-secret.yaml new file mode 100644 index 000000000..571688e40 --- /dev/null +++ b/docs/examples/qdrant/backup/logical/encryption-secret.yaml @@ -0,0 +1,8 @@ +apiVersion: v1 +kind: Secret +type: Opaque +metadata: + name: encrypt-secret + namespace: default +stringData: + RESTIC_PASSWORD: "changeit" \ No newline at end of file diff --git a/docs/examples/qdrant/backup/logical/minio.yaml b/docs/examples/qdrant/backup/logical/minio.yaml new file mode 100644 index 000000000..5955390a6 --- /dev/null +++ b/docs/examples/qdrant/backup/logical/minio.yaml @@ -0,0 +1,78 @@ +--- +# Minimal MinIO Deployment for Qdrant Snapshots +apiVersion: v1 +kind: Namespace +metadata: + name: default +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: minio + namespace: default +spec: + selector: + matchLabels: + app: minio + template: + metadata: + labels: + app: minio + spec: + containers: + - name: minio + image: minio/minio:latest + args: + - server + - /data + - --console-address + - ":9001" + env: + - name: MINIO_ROOT_USER + value: minioadmin + - name: MINIO_ROOT_PASSWORD + value: minioadmin + ports: + - containerPort: 9000 + - containerPort: 9001 + volumeMounts: + - name: data + mountPath: /data + volumes: + - name: data + emptyDir: {} +--- +apiVersion: v1 +kind: Service +metadata: + name: minio + namespace: default +spec: + ports: + - name: api + port: 9000 + - name: console + port: 9001 + selector: + app: minio +--- +apiVersion: batch/v1 +kind: Job +metadata: + name: minio-setup + namespace: default +spec: + template: + spec: + containers: + - name: mc + image: minio/mc:latest + command: + - /bin/sh + - -c + - | + sleep 10 + mc alias set myminio http://minio:9000 minioadmin minioadmin + mc mb myminio/qdrant-backups --ignore-existing + echo "Bucket created" + restartPolicy: Never \ No newline at end of file diff --git a/docs/examples/qdrant/backup/logical/qdrant-addon.yaml b/docs/examples/qdrant/backup/logical/qdrant-addon.yaml new file mode 100644 index 000000000..23cba00e6 --- /dev/null +++ b/docs/examples/qdrant/backup/logical/qdrant-addon.yaml @@ -0,0 +1,116 @@ +apiVersion: addons.kubestash.com/v1alpha1 +kind: Addon +metadata: + name: qdrant-addon +spec: + backupTasks: + - name: logical-backup + function: qdrant-backup + driver: Restic + executor: Job + singleton: true + parameters: + - name: collections + usage: Comma-separated list of collections to backup. If not specified, all collections will be backed up. + required: false + - name: args + usage: Arguments to be passed to the dump command. + required: false + - name: enableCache + usage: Enable or disable caching. Disabling caching may impact backup performance. + required: false + default: "true" + - name: scratchDir + usage: Directory for holding temporary files and restic cache. + required: false + default: /kubestash-tmp + volumeTemplate: + - name: kubestash-tmp-volume + usage: Holds temporary files and restic cache. + source: + emptyDir: {} + volumeMounts: + - name: kubestash-tmp-volume + mountPath: /kubestash-tmp + - name: volume-snapshot + function: qdrant-csi-snapshotter + driver: VolumeSnapshotter + executor: Job + singleton: true + parameters: + - name: volumeSnapshotClassName + usage: The VolumeSnapshotClassName to be used by volumeSnapshot + required: false + - name: manifest-backup + function: kubedbmanifest-backup + driver: Restic + executor: Job + singleton: true + parameters: + - name: enableCache + usage: Enable or disable caching. Disabling caching may impact backup performance. + required: false + default: "true" + - name: scratchDir + usage: Directory for holding temporary files and restic cache. + required: false + default: /kubestash-tmp + volumeTemplate: + - name: kubestash-tmp-volume + usage: Holds temporary files and restic cache. + source: + emptyDir: {} + volumeMounts: + - name: kubestash-tmp-volume + mountPath: /kubestash-tmp + restoreTasks: + - name: logical-backup-restore + function: qdrant-restore + driver: Restic + executor: Job + singleton: true + parameters: + - name: collections + usage: Comma-separated list of collections to restore. If not provided, all collections will be restored. + required: false + - name: args + usage: Arguments to be passed to the dump command. + required: false + - name: enableCache + usage: Enable or disable caching. Disabling caching may impact backup performance. + required: false + default: "true" + - name: scratchDir + usage: Directory for holding temporary files and restic cache. + required: false + default: /kubestash-tmp + volumeTemplate: + - name: kubestash-tmp-volume + usage: Holds temporary files and restic cache. + source: + emptyDir: {} + volumeMounts: + - name: kubestash-tmp-volume + mountPath: /kubestash-tmp + - name: manifest-restore + function: kubedbmanifest-restore + driver: Restic + executor: Job + singleton: true + parameters: + - name: enableCache + usage: Enable or disable caching. Disabling caching may impact backup performance. + required: false + default: "true" + - name: scratchDir + usage: Directory for holding temporary files and restic cache. + required: false + default: /kubestash-tmp + volumeTemplate: + - name: kubestash-tmp-volume + usage: Holds temporary files and restic cache. + source: + emptyDir: {} + volumeMounts: + - name: kubestash-tmp-volume + mountPath: /kubestash-tmp \ No newline at end of file diff --git a/docs/examples/qdrant/backup/logical/qdrant-backup-function.yaml b/docs/examples/qdrant/backup/logical/qdrant-backup-function.yaml new file mode 100644 index 000000000..766e7d65c --- /dev/null +++ b/docs/examples/qdrant/backup/logical/qdrant-backup-function.yaml @@ -0,0 +1,14 @@ +apiVersion: addons.kubestash.com/v1alpha1 +kind: Function +metadata: + name: qdrant-backup +spec: + args: + - backup + - --namespace=${namespace:=default} + - --backupsession=${backupSession:=} + - --enable-cache=${enableCache:=} + - --scratch-dir=${scratchDir:=} + - --wait-timeout=${waitTimeout:=300} + - --collections=${collections:=} + image: almasood/qdrant-restic-plugin:20260428144910 \ No newline at end of file diff --git a/docs/examples/qdrant/backup/logical/qdrant-restore-function.yaml b/docs/examples/qdrant/backup/logical/qdrant-restore-function.yaml new file mode 100644 index 000000000..ad9ba17ee --- /dev/null +++ b/docs/examples/qdrant/backup/logical/qdrant-restore-function.yaml @@ -0,0 +1,15 @@ +apiVersion: addons.kubestash.com/v1alpha1 +kind: Function +metadata: + name: qdrant-restore +spec: + args: + - restore + - --namespace=${namespace:=default} + - --restoresession=${restoreSession:=} + - --snapshot=${snapshot:=} + - --enable-cache=${enableCache:=} + - --scratch-dir=${scratchDir:=} + - --wait-timeout=${waitTimeout:=300} + - --collections=${collections:=} + image: almasood/qdrant-restic-plugin:20260428144910 diff --git a/docs/examples/qdrant/backup/logical/retention-policy.yaml b/docs/examples/qdrant/backup/logical/retention-policy.yaml new file mode 100644 index 000000000..fffcfcd0d --- /dev/null +++ b/docs/examples/qdrant/backup/logical/retention-policy.yaml @@ -0,0 +1,15 @@ +apiVersion: storage.kubestash.com/v1alpha1 +kind: RetentionPolicy +metadata: + name: demo-retention + namespace: default +spec: + default: true + failedSnapshots: + last: 2 + maxRetentionPeriod: 2mo + successfulSnapshots: + last: 5 + usagePolicy: + allowedNamespaces: + from: All \ No newline at end of file diff --git a/docs/examples/qdrant/backup/volumesnapshot/backup-configuration.yaml b/docs/examples/qdrant/backup/volumesnapshot/backup-configuration.yaml new file mode 100644 index 000000000..635840e6b --- /dev/null +++ b/docs/examples/qdrant/backup/volumesnapshot/backup-configuration.yaml @@ -0,0 +1,33 @@ +apiVersion: core.kubestash.com/v1alpha1 +kind: BackupConfiguration +metadata: + name: sample-qdrant-backup + namespace: default +spec: + target: + apiGroup: kubedb.com + kind: Qdrant + namespace: default + name: qdrant-sample + backends: + - name: minio-storage + storageRef: + name: minio-storage + namespace: default + sessions: + - name: frequent-backup + scheduler: + schedule: "*/5 * * * *" + jobTemplate: + backoffLimit: 1 + repositories: + - name: minio-qdrant-repo + backend: minio-storage + directory: /qdrant + encryptionSecret: + name: encrypt-secret + namespace: default + addon: + name: qdrant-addon + tasks: + - name: volume-snapshot diff --git a/docs/examples/qdrant/backup/volumesnapshot/backup-storage.yaml b/docs/examples/qdrant/backup/volumesnapshot/backup-storage.yaml new file mode 100644 index 000000000..53bef0c25 --- /dev/null +++ b/docs/examples/qdrant/backup/volumesnapshot/backup-storage.yaml @@ -0,0 +1,20 @@ +apiVersion: storage.kubestash.com/v1alpha1 +kind: BackupStorage +metadata: + name: minio-storage + namespace: default +spec: + storage: + provider: s3 + s3: + bucket: qdrant-backups + endpoint: http://minio.default.svc:9000 + insecureTLS: true + prefix: backup/demo + region: us-east-1 + secretName: aws-secret + usagePolicy: + allowedNamespaces: + from: All + default: true + deletionPolicy: Delete \ No newline at end of file diff --git a/docs/examples/qdrant/backup/volumesnapshot/encryption-secret.yaml b/docs/examples/qdrant/backup/volumesnapshot/encryption-secret.yaml new file mode 100644 index 000000000..571688e40 --- /dev/null +++ b/docs/examples/qdrant/backup/volumesnapshot/encryption-secret.yaml @@ -0,0 +1,8 @@ +apiVersion: v1 +kind: Secret +type: Opaque +metadata: + name: encrypt-secret + namespace: default +stringData: + RESTIC_PASSWORD: "changeit" \ No newline at end of file diff --git a/docs/examples/qdrant/backup/volumesnapshot/qdrant-addon.yaml b/docs/examples/qdrant/backup/volumesnapshot/qdrant-addon.yaml new file mode 100644 index 000000000..b154b92fd --- /dev/null +++ b/docs/examples/qdrant/backup/volumesnapshot/qdrant-addon.yaml @@ -0,0 +1,21 @@ +apiVersion: addons.kubestash.com/v1alpha1 +kind: Addon +metadata: + name: qdrant-addon +spec: + backupTasks: + - name: volume-snapshot + function: qdrant-csi-snapshotter + driver: VolumeSnapshotter + executor: Job + singleton: true + parameters: + - name: volumeSnapshotClassName + usage: The VolumeSnapshotClassName to be used by volumeSnapshot + required: false + restoreTasks: + - name: volume-snapshot-restore + function: qdrant-csi-snapshotter + driver: VolumeSnapshotter + executor: Job + singleton: true diff --git a/docs/examples/qdrant/backup/volumesnapshot/qdrant-csi-snapshotter-function.yaml b/docs/examples/qdrant/backup/volumesnapshot/qdrant-csi-snapshotter-function.yaml new file mode 100644 index 000000000..1aa544cc1 --- /dev/null +++ b/docs/examples/qdrant/backup/volumesnapshot/qdrant-csi-snapshotter-function.yaml @@ -0,0 +1,11 @@ +apiVersion: addons.kubestash.com/v1alpha1 +kind: Function +metadata: + name: qdrant-csi-snapshotter +spec: + args: + - volume-snapshot + - --namespace=${namespace:=default} + - --backupsession=${backupSession:=} + - --wait-timeout=${waitTimeout:=300} + image: almasood/qdrant-restic-plugin:20260428144910 diff --git a/docs/examples/qdrant/backup/volumesnapshot/qdrant-restore.yaml b/docs/examples/qdrant/backup/volumesnapshot/qdrant-restore.yaml new file mode 100644 index 000000000..8fa605fc3 --- /dev/null +++ b/docs/examples/qdrant/backup/volumesnapshot/qdrant-restore.yaml @@ -0,0 +1,17 @@ +apiVersion: kubedb.com/v1alpha2 +kind: Qdrant +metadata: + name: qdrant-sample-restore + namespace: demo +spec: + version: 1.17.0 + mode: Distributed + replicas: 3 + storage: + storageClassName: longhorn + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 200Mi + deletionPolicy: WipeOut diff --git a/docs/examples/qdrant/backup/volumesnapshot/restore-session.yaml b/docs/examples/qdrant/backup/volumesnapshot/restore-session.yaml new file mode 100644 index 000000000..9352d0cee --- /dev/null +++ b/docs/examples/qdrant/backup/volumesnapshot/restore-session.yaml @@ -0,0 +1,21 @@ +apiVersion: core.kubestash.com/v1alpha1 +kind: RestoreSession +metadata: + name: restore-sample-qdrant + namespace: demo +spec: + target: + apiGroup: kubedb.com + kind: Qdrant + namespace: demo + name: qdrant-sample-restore + dataSource: + repository: minio-qdrant-repo + snapshot: latest + encryptionSecret: + name: encrypt-secret + namespace: demo + addon: + name: qdrant-addon + tasks: + - name: volume-snapshot-restore diff --git a/docs/examples/qdrant/backup/volumesnapshot/volumesnapshotclass.yaml b/docs/examples/qdrant/backup/volumesnapshot/volumesnapshotclass.yaml new file mode 100644 index 000000000..176829012 --- /dev/null +++ b/docs/examples/qdrant/backup/volumesnapshot/volumesnapshotclass.yaml @@ -0,0 +1,8 @@ +apiVersion: snapshot.storage.k8s.io/v1 +kind: VolumeSnapshotClass +metadata: + name: longhorn-snapshot-vsc +driver: driver.longhorn.io +deletionPolicy: Delete +parameters: + type: bak \ No newline at end of file diff --git a/docs/examples/qdrant/quickstart/yamls/qdrant-distributed.yaml b/docs/examples/qdrant/quickstart/yamls/qdrant-distributed.yaml new file mode 100644 index 000000000..56301a747 --- /dev/null +++ b/docs/examples/qdrant/quickstart/yamls/qdrant-distributed.yaml @@ -0,0 +1,28 @@ +apiVersion: kubedb.com/v1alpha2 +kind: Qdrant +metadata: + name: qdrant-sample + namespace: demo +spec: + version: 1.17.0 + mode: Distributed + replicas: 3 + storage: + storageClassName: longhorn + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 200Mi + podTemplate: + spec: + containers: + - name: qdrant + resources: + requests: + cpu: 100m + memory: 100Mi + limits: + cpu: 100m + memory: 100Mi + deletionPolicy: WipeOut diff --git a/docs/examples/qdrant/quickstart/yamls/qdrant-hor-scaling.yaml b/docs/examples/qdrant/quickstart/yamls/qdrant-hor-scaling.yaml new file mode 100644 index 000000000..9a9138fd8 --- /dev/null +++ b/docs/examples/qdrant/quickstart/yamls/qdrant-hor-scaling.yaml @@ -0,0 +1,11 @@ +apiVersion: ops.kubedb.com/v1alpha1 +kind: QdrantOpsRequest +metadata: + name: qdrant-hor-scaling + namespace: demo +spec: + type: HorizontalScaling + databaseRef: + name: qdrant-sample + horizontalScaling: + node: 5 diff --git a/docs/guides/qdrant/autoscaler/_index.md b/docs/guides/qdrant/autoscaler/_index.md index 2abd242bd..c2349712a 100644 --- a/docs/guides/qdrant/autoscaler/_index.md +++ b/docs/guides/qdrant/autoscaler/_index.md @@ -5,6 +5,6 @@ menu: identifier: qdrant-autoscaler name: Autoscaler parent: qdrant-guides - weight: 130 + weight: 140 menu_name: docs_{{ .version }} --- diff --git a/docs/guides/qdrant/backup/_index.md b/docs/guides/qdrant/backup/_index.md index c4dc974b7..2bc61031c 100644 --- a/docs/guides/qdrant/backup/_index.md +++ b/docs/guides/qdrant/backup/_index.md @@ -1,10 +1,10 @@ --- -title: Qdrant Backup +title: Qdrant Backup & Restore menu: docs_{{ .version }}: identifier: qdrant-backup name: Backup & Restore parent: qdrant-guides - weight: 50 + weight: 60 menu_name: docs_{{ .version }} --- diff --git a/docs/guides/qdrant/backup/logical/index.md b/docs/guides/qdrant/backup/logical/index.md new file mode 100644 index 000000000..0085fc849 --- /dev/null +++ b/docs/guides/qdrant/backup/logical/index.md @@ -0,0 +1,526 @@ +--- +title: Backup & Restore Qdrant | KubeStash +description: Backup Qdrant database using KubeStash +menu: + docs_{{ .version }}: + identifier: guides-qdrant-logical-backup + name: Logical Backup + parent: qdrant-backup + weight: 10 +menu_name: docs_{{ .version }} +section_menu_id: guides +--- + +# Backup and Restore Qdrant Database Using KubeStash + +KubeStash allows you to backup and restore `Qdrant` databases logically. This guide will show you how to take logical backup and restore your `Qdrant` databases using KubeStash. + +## Before You Begin + +- At first, you need to have a Kubernetes cluster, and the `kubectl` command-line tool must be configured to communicate with your cluster. If you do not already have a cluster, you can create one by using `Minikube` or `Kind`. +- Install `KubeDB` in your cluster following the steps [here](/docs/setup/README.md). +- Install `KubeStash` in your cluster following the steps [here](https://kubestash.com/docs/latest/setup/install/kubestash). +- Install KubeStash `kubectl` plugin following the steps [here](https://kubestash.com/docs/latest/setup/install/kubectl-plugin/). +- If you are not familiar with how KubeStash backup and restore Qdrant databases, please check the following guide [here](/docs/guides/qdrant/backup/overview/index.md). + +You should be familiar with the following `KubeStash` concepts: + +- [BackupStorage](https://kubestash.com/docs/latest/concepts/crds/backupstorage/) +- [BackupConfiguration](https://kubestash.com/docs/latest/concepts/crds/backupconfiguration/) +- [BackupSession](https://kubestash.com/docs/latest/concepts/crds/backupsession/) +- [RestoreSession](https://kubestash.com/docs/latest/concepts/crds/restoresession/) +- [Addon](https://kubestash.com/docs/latest/concepts/crds/addon/) +- [Function](https://kubestash.com/docs/latest/concepts/crds/function/) +- [Task](https://kubestash.com/docs/latest/concepts/crds/addon/#task-specification) + +To keep everything isolated, we are going to use a separate namespace called `demo` throughout this tutorial. + +```bash +$ kubectl create ns demo +namespace/demo created +``` + +> **Note:** YAML files used in this tutorial are stored in [docs/guides/qdrant/backup/logical/examples](docs/guides/qdrant/backup/logical/examples) directory of [kubedb/docs](https://github.com/kubedb/docs) repository. + +## Backup Qdrant + +KubeStash supports logical backup for `Qdrant` databases. In this demonstration, we'll backup a Qdrant database into a S3-compatible storage (MinIO). + +This section will demonstrate how to backup a `Qdrant` database. Here, we are going to deploy a `Qdrant` database using KubeDB. Then, we are going to backup this database into a `MinIO` bucket. Finally, we will restore the backed up data into another `Qdrant` database. + +### Deploy Sample Qdrant Database + +Let's deploy a sample `Qdrant` database and insert some data into it. + +**Create Qdrant CR:** + +Below is the YAML of a sample `Qdrant` CRD that we are going to create for this tutorial: + +```yaml +apiVersion: kubedb.com/v1 +kind: Qdrant +metadata: + name: sample-qdrant + namespace: demo +spec: + version: "1.17.0" + mode: Distributed + replicas: 3 + storage: + storageClassName: longhorn + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 200Mi + deletionPolicy: WipeOut +``` + +Create the above `Qdrant` CR, + +```bash +$ kubectl apply -f https://github.com/kubedb/docs/raw/{{< param "info.version" >}}/docs/guides/qdrant/backup/logical/examples/sample-qdrant.yaml +qdrant.kubedb.com/sample-qdrant created +``` + +KubeDB will deploy a Qdrant database according to the above specification. It will also create the necessary `Secrets` and `Services` to access the database. + +Let's check if the database is ready to use, + +```bash +$ kubectl get qdrant -n demo +NAME VERSION STATUS AGE +sample-qdrant 1.17.0 Ready 4m22s +``` + +The database is `Ready`. Verify that KubeDB has created a `Secret` and a `Service` for this database using the following commands, + +```bash +$ kubectl get secret -n demo -l=app.kubernetes.io/instance=sample-qdrant +NAME TYPE DATA AGE +sample-qdrant-auth Opaque 2 4m58s + +$ kubectl get service -n demo -l=app.kubernetes.io/instance=sample-qdrant +NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE +sample-qdrant ClusterIP 10.96.55.61 6333/TCP 97s +sample-qdrant-pods ClusterIP None 6333/TCP 97s +``` + +KubeDB creates an [AppBinding](/docs/guides/qdrant/concepts/appbinding/index.md) CR that holds the necessary information to connect with the database. + +**Verify AppBinding:** + +Verify that the `AppBinding` has been created successfully using the following command, + +```bash +$ kubectl get appbindings -n demo +NAME AGE +sample-qdrant 9m24s +``` + +**Insert Sample Data:** + +Now, we are going to exec into the database pod and create some sample data. At first, find out the database `Pod` using the following command, + +```bash +$ kubectl get pods -n demo --selector="app.kubernetes.io/instance=sample-qdrant" +NAME READY STATUS RESTARTS AGE +sample-qdrant-0 1/1 Running 0 2m41s +sample-qdrant-1 1/1 Running 0 2m35s +sample-qdrant-2 1/1 Running 0 2m29s +``` + +Now, let's exec into the `Pod` to insert some sample data into Qdrant: + +```bash +$ kubectl exec -it -n demo sample-qdrant-0 -- sh +# Upload some sample points to a collection +$ wget -qO- --header 'Content-Type: application/json' \ + --post-data '{ + "vectors": [ + {"id": 1, "vector": [0.1, 0.2, 0.3, 0.4]}, + {"id": 2, "vector": [0.5, 0.6, 0.7, 0.8]} + ] + }' \ + http://localhost:6333/collections/my_collection/points +# Exit the pod +$ exit +``` + +Now, we are ready to backup the database. + +### Prepare Backend + +We are going to store our backed up data into a MinIO bucket. We have to create a Secret with necessary credentials and a `BackupStorage` CR to use this backend. If you want to use a different backend, please read the respective backend configuration doc from [here](https://kubestash.com/docs/latest/guides/backends/overview/). + +**Deploy MinIO:** + +Let's deploy MinIO in the `demo` namespace: + +```bash +$ kubectl apply -f https://github.com/kubedb/docs/raw/{{< param "info.version" >}}/docs/guides/qdrant/backup/logical/examples/minio.yaml +deployment.apps/minio created +service/minio created +job.batch/minio-setup created +``` + +Verify MinIO is running: + +```bash +$ kubectl get pods -n demo -l app=minio +NAME READY STATUS RESTARTS AGE +minio-xxxxxxxxxx-xxxxx 1/1 Running 0 2m +``` + +**Create Storage Secret:** + +Create a secret with credentials to access the MinIO storage: + +```bash +$ kubectl apply -f https://github.com/kubedb/docs/raw/{{< param "info.version" >}}/docs/guides/qdrant/backup/logical/examples/aws-secret.yaml +secret/aws-secret created +``` + +**Create BackupStorage:** + +Create a `BackupStorage` CR to configure the backup storage: + +```yaml +apiVersion: storage.kubestash.com/v1alpha1 +kind: BackupStorage +metadata: + name: minio-storage + namespace: demo +spec: + storage: + provider: s3 + s3: + bucket: qdrant-backups + endpoint: http://minio.demo.svc:9000 + insecureTLS: true + prefix: backup/demo + region: us-east-1 + secretName: aws-secret + usagePolicy: + allowedNamespaces: + from: All + default: true + deletionPolicy: Delete +``` + +Apply the BackupStorage: + +```bash +$ kubectl apply -f https://github.com/kubedb/docs/raw/{{< param "info.version" >}}/docs/guides/qdrant/backup/logical/examples/backupstorage.yaml +backupstorage.storage.kubestash.com/minio-storage created +``` + +**Create Encryption Secret:** + +Create a secret for encrypting the backup data: + +```bash +$ echo -n 'changeit' > RESTIC_PASSWORD +$ kubectl create secret generic -n demo encrypt-secret \ + --from-file=./RESTIC_PASSWORD +secret "encrypt-secret" created +``` + +**Create RetentionPolicy:** + +Now, let's create a `RetentionPolicy` to specify how the old Snapshots should be cleaned up. + +Below is the YAML of the `RetentionPolicy` object that we are going to create, + +```yaml +apiVersion: storage.kubestash.com/v1alpha1 +kind: RetentionPolicy +metadata: + name: demo-retention + namespace: demo +spec: + default: true + maxNumberOfSnapshots: 5 + usagePolicy: + allowedNamespaces: + from: All +``` + +Let's create the above `RetentionPolicy`, + +```bash +$ kubectl apply -f https://github.com/kubedb/docs/raw/{{< param "info.version" >}}/docs/guides/qdrant/backup/logical/examples/retentionpolicy.yaml +retentionpolicy.storage.kubestash.com/demo-retention created +``` + +### Backup + +We have to create a `BackupConfiguration` targeting respective `sample-qdrant` Qdrant database. Then, KubeStash will create a `CronJob` for each session to take periodic backup of that database. + +**Create BackupConfiguration:** + +Below is the YAML for `BackupConfiguration` CR to backup the `sample-qdrant` database that we have deployed earlier, + +```yaml +apiVersion: core.kubestash.com/v1alpha1 +kind: BackupConfiguration +metadata: + name: sample-qdrant-backup + namespace: demo +spec: + target: + apiGroup: kubedb.com + kind: Qdrant + namespace: demo + name: sample-qdrant + backends: + - name: minio-backend + storageRef: + namespace: demo + name: minio-storage + retentionPolicy: + name: demo-retention + namespace: demo + sessions: + - name: frequent-backup + scheduler: + schedule: "*/5 * * * *" + jobTemplate: + backoffLimit: 1 + repositories: + - name: minio-qdrant-repo + backend: minio-backend + directory: /qdrant + encryptionSecret: + name: encrypt-secret + namespace: demo + addon: + name: qdrant-addon + tasks: + - name: logical-backup + params: + collections: "my_collection" +``` + +Let's create the `BackupConfiguration` CR that we have shown above, + +```bash +$ kubectl apply -f https://github.com/kubedb/docs/raw/{{< param "info.version" >}}/docs/guides/qdrant/backup/logical/examples/backupconfiguration.yaml +backupconfiguration.core.kubestash.com/sample-qdrant-backup created +``` + +**Verify Backup Setup Successful:** + +If everything goes well, the phase of the `BackupConfiguration` should be `Ready`. The `Ready` phase indicates that the backup setup is successful. Let's verify the `Phase` of the BackupConfiguration, + +```bash +$ kubectl get backupconfiguration -n demo +NAME PHASE PAUSED AGE +sample-qdrant-backup Ready 2m50s +``` + +Additionally, we can verify that the `Repository` specified in the `BackupConfiguration` has been created using the following command, + +```bash +$ kubectl get repo -n demo +NAME INTEGRITY SNAPSHOT-COUNT SIZE PHASE LAST-SUCCESSFUL-BACKUP AGE +minio-qdrant-repo 0 0 B Ready 3m +``` + +**Verify CronJob:** + +It will also create a `CronJob` with the schedule specified in `spec.sessions[*].scheduler.schedule` field of `BackupConfiguration` CR. + +Verify that the `CronJob` has been created using the following command, + +```bash +$ kubectl get cronjob -n demo +NAME SCHEDULE SUSPEND ACTIVE LAST SCHEDULE AGE +trigger-sample-qdrant-backup-frequent-backup */5 * * * * 0 2m45s 3m25s +``` + +**Verify BackupSession:** + +KubeStash triggers an instant backup as soon as the `BackupConfiguration` is ready. After that, backups are scheduled according to the specified schedule. + +```bash +$ kubectl get backupsession -n demo -w + +NAME INVOKER-TYPE INVOKER-NAME PHASE DURATION AGE +sample-qdrant-backup-frequent-backup-xyz BackupConfiguration sample-qdrant-backup Succeeded 7m22s +``` + +We can see from the above output that the backup session has succeeded. Now, we are going to verify whether the backed up data has been stored in the backend. + +**Verify Backup:** + +Once a backup is complete, KubeStash will update the respective `Repository` CR to reflect the backup. Check that the repository `minio-qdrant-repo` has been updated by the following command, + +```bash +$ kubectl get repository -n demo minio-qdrant-repo +NAME INTEGRITY SNAPSHOT-COUNT SIZE PHASE LAST-SUCCESSFUL-BACKUP AGE +minio-qdrant-repo true 1 806 B Ready 8m27s 9m18s +``` + +Run the following command to check the respective `Snapshot` which represents the state of a backup run for an application. + +```bash +$ kubectl get snapshots -n demo -l=kubestash.com/repo-name=minio-qdrant-repo +NAME REPOSITORY SESSION SNAPSHOT-TIME DELETION-POLICY PHASE AGE +minio-qdrant-repo-sample-qdrant-backup-frequent-backup-xyz minio-qdrant-repo frequent-backup 2024-01-23T13:10:54Z Delete Succeeded 16h +``` + +> Note: KubeStash creates a `Snapshot` with the following labels: +> - `kubestash.com/app-ref-kind: ` +> - `kubestash.com/app-ref-name: ` +> - `kubestash.com/app-ref-namespace: ` +> - `kubestash.com/repo-name: ` +> +> These labels can be used to watch only the `Snapshot`s related to our target Database or `Repository`. + +> KubeStash uses `qdrant-restic-plugin` to perform backups of target `Qdrant` databases. Therefore, the component name for logical backups is set as `dump`. + +## Restore + +In this section, we are going to restore the database from the backup we have taken in the previous section. We are going to deploy a new database and initialize it from the backup. + +#### Deploy Restored Database: + +Now, we have to deploy the restored database similarly as we have deployed the original `sample-qdrant` database. However, this time there will be the following differences: + +- We are going to specify `.spec.init.waitForInitialRestore` field that tells KubeDB to wait for first restore to complete before marking this database is ready to use. + +Below is the YAML for `Qdrant` CRD we are going deploy to initialize from backup, + +```yaml +apiVersion: kubedb.com/v1 +kind: Qdrant +metadata: + name: restored-qdrant + namespace: demo +spec: + init: + waitForInitialRestore: true + version: "1.17.0" + mode: Distributed + replicas: 3 + storage: + storageClassName: longhorn + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 200Mi + deletionPolicy: WipeOut +``` + +Let's create the above database, + +```bash +$ kubectl apply -f https://github.com/kubedb/docs/raw/{{< param "info.version" >}}/docs/guides/qdrant/backup/logical/examples/restored-qdrant.yaml +qdrant.kubedb.com/restored-qdrant created +``` + +If you check the database status, you will see it is stuck in `Provisioning` state. + +```bash +$ kubectl get qdrant -n demo restored-qdrant +NAME VERSION STATUS AGE +restored-qdrant 1.17.0 Provisioning 61s +``` + +#### Create RestoreSession: + +Now, we need to create a RestoreSession CRD pointing to targeted `Qdrant` database. + +Below, is the contents of YAML file of the `RestoreSession` object that we are going to create to restore backed up data into the newly created database provisioned by Qdrant object named `restored-qdrant`. + +```yaml +apiVersion: core.kubestash.com/v1alpha1 +kind: RestoreSession +metadata: + name: restore-sample-qdrant + namespace: demo +spec: + target: + apiGroup: kubedb.com + kind: Qdrant + namespace: demo + name: restored-qdrant + dataSource: + repository: minio-qdrant-repo + snapshot: latest + encryptionSecret: + name: encrypt-secret + namespace: demo + addon: + name: qdrant-addon + tasks: + - name: logical-backup-restore +``` + +Let's create the RestoreSession CRD object we have shown above, + +```bash +$ kubectl apply -f https://github.com/kubedb/docs/raw/{{< param "info.version" >}}/docs/guides/qdrant/backup/logical/examples/restoresession.yaml +restoresession.core.kubestash.com/restore-sample-qdrant created +``` + +Once, you have created the `RestoreSession` object, KubeStash will create restore Job. Run the following command to watch the phase of the `RestoreSession` object, + +```bash +$ watch kubectl get restoresession -n demo +Every 2.0s: kubectl get restores... AppsCode-PC-03: Wed Aug 21 10:44:05 2024 + +NAME REPOSITORY FAILURE-POLICY PHASE DURATION AGE +restore-sample-qdrant minio-qdrant-repo Succeeded 3s 53s +``` + +The `Succeeded` phase means that the restore process has been completed successfully. + +#### Verify Restored Data: + +In this section, we are going to verify whether the desired data has been restored successfully. We are going to connect to the database server and check whether the collection we created earlier in the original database are restored. + +At first, check if the database has gone into `Ready` state by the following command, + +```bash +$ kubectl get qdrant -n demo restored-qdrant +NAME VERSION STATUS AGE +restored-qdrant 1.17.0 Ready 34m +``` + +Now, find out the database `Pod` by the following command, + +```bash +$ kubectl get pods -n demo --selector="app.kubernetes.io/instance=restored-qdrant" +NAME READY STATUS RESTARTS AGE +restored-qdrant-0 1/1 Running 0 39m +``` + +Now, let's exec into the Pod to enter into Qdrant and verify restored data, + +```bash +$ kubectl exec -it -n demo restored-qdrant-0 -- sh +# Check if the collection exists and has data +$ wget -qO- http://localhost:6333/collections/my_collection +# Exit the pod +$ exit +``` + +So, from the above output, we can see that the `my_collection` collection we created earlier in the original database and now, it is restored successfully. + +## Cleanup + +To cleanup the Kubernetes resources created by this tutorial, run: + +```bash +kubectl delete backupconfigurations.core.kubestash.com -n demo sample-qdrant-backup +kubectl delete restoresessions.core.kubestash.com -n demo restore-sample-qdrant +kubectl delete retentionpolicies.storage.kubestash.com -n demo demo-retention +kubectl delete backupstorage -n demo minio-storage +kubectl delete secret -n demo aws-secret +kubectl delete secret -n demo encrypt-secret +kubectl delete qdrant -n demo restored-qdrant +kubectl delete qdrant -n demo sample-qdrant +``` diff --git a/docs/guides/qdrant/backup/overview.md b/docs/guides/qdrant/backup/overview.md deleted file mode 100644 index 61dac7201..000000000 --- a/docs/guides/qdrant/backup/overview.md +++ /dev/null @@ -1,58 +0,0 @@ ---- -title: Qdrant Backup Overview -menu: - docs_{{ .version }}: - identifier: qdrant-backup-overview - name: Overview - parent: qdrant-backup - weight: 10 -menu_name: docs_{{ .version }} -section_menu_id: guides ---- - -> New to KubeDB? Please start [here](/docs/README.md). - -# Qdrant Backup Overview - -This guide will give an overview of how KubeDB supports backup and restore for `Qdrant` databases using [KubeStash](https://kubestash.com). - -## Before You Begin - -- You should be familiar with the following `KubeDB` concepts: - - [Qdrant](/docs/guides/qdrant/concepts/qdrant.md) -- You should be familiar with the following `KubeStash` concepts: - - [BackupStorage](https://kubestash.com/docs/latest/concepts/crds/backupstorage/) - - [BackupConfiguration](https://kubestash.com/docs/latest/concepts/crds/backupconfiguration/) - - [BackupSession](https://kubestash.com/docs/latest/concepts/crds/backupsession/) - - [RestoreSession](https://kubestash.com/docs/latest/concepts/crds/restoresession/) - - [RetentionPolicy](https://kubestash.com/docs/latest/concepts/crds/retentionpolicy/) - -## How Backup Works - -KubeStash uses a sidecar-based approach to backup Qdrant databases. The backup process consists of the following steps: - -1. At first, a user creates a `BackupStorage` CR that defines the backend storage location (e.g., S3, GCS, Azure Blob). - -2. Then, the user creates a `RetentionPolicy` CR that defines how long backup snapshots will be retained. - -3. Then, the user creates a `BackupConfiguration` CR that references the target `Qdrant` database, the `BackupStorage`, and the `RetentionPolicy`. A backup schedule (cron expression) can be defined. - -4. When a `BackupConfiguration` CR is created, KubeStash creates a `CronJob` to trigger backup sessions at the scheduled time. - -5. On each scheduled time, a `BackupSession` CR is created. KubeStash executes the backup in a temporary job that connects to the Qdrant database and writes a snapshot to the backend storage. - -6. The backup snapshot is stored in the backend storage and a `Snapshot` CR is created to track the backup metadata. - -## How Restore Works - -The restore process consists of the following steps: - -1. At first, the user creates a target `Qdrant` database (or uses an existing one). - -2. Then, the user creates a `RestoreSession` CR referencing the `Snapshot` to restore and the target `Qdrant` database. - -3. KubeStash executes the restore in a temporary job that reads the snapshot from the backend storage and restores the data to the target Qdrant database. - -4. After the restore completes, the `RestoreSession` status transitions to `Succeeded`. - -In the next docs, we are going to show step-by-step guides on backup and restore of Qdrant databases using KubeStash. diff --git a/docs/guides/qdrant/backup/volumesnapshot/index.md b/docs/guides/qdrant/backup/volumesnapshot/index.md new file mode 100644 index 000000000..4079bd864 --- /dev/null +++ b/docs/guides/qdrant/backup/volumesnapshot/index.md @@ -0,0 +1,478 @@ +--- +title: Volume Snapshot Backup & Restore Qdrant +description: Backup Qdrant database using Volume Snapshot +menu: + docs_{{ .version }}: + identifier: guides-qdrant-volume-snapshot + name: Volume Snapshot + parent: qdrant-backup + weight: 20 +menu_name: docs_{{ .version }} +section_menu_id: guides +--- + +# Volume Snapshot Backup and Restore Qdrant Database + +KubeStash allows you to take volume snapshot backups of Qdrant databases. Volume snapshots provide a fast and efficient way to backup and restore the entire storage volume of your Qdrant cluster. This guide will show you how to configure volume snapshot backup and restore for Qdrant databases. + +## Before You Begin + +- At first, you need to have a Kubernetes cluster, and the `kubectl` command-line tool must be configured to communicate with your cluster. If you do not already have a cluster, you can create one by using `Minikube` or `Kind`. +- Install `KubeDB` in your cluster following the steps [here](/docs/setup/README.md). +- Install `KubeStash` in your cluster following the steps [here](https://kubestash.com/docs/latest/setup/install/kubestash). +- Install KubeStash `kubectl` plugin following the steps [here](https://kubestash.com/docs/latest/setup/install/kubectl-plugin/). +- Ensure your storage provider supports VolumeSnapshots (e.g., Longhorn, AWS EBS, GCE PD). +- If you are not familiar with how KubeStash backup and restore Qdrant databases, please check the following guide [here](/docs/guides/qdrant/backup/overview/index.md). + +You should be familiar with the following `KubeStash` concepts: + +- [BackupStorage](https://kubestash.com/docs/latest/concepts/crds/backupstorage/) +- [BackupConfiguration](https://kubestash.com/docs/latest/concepts/crds/backupconfiguration/) +- [BackupSession](https://kubestash.com/docs/latest/concepts/crds/backupsession/) +- [RestoreSession](https://kubestash.com/docs/latest/concepts/crds/restoresession/) +- [Addon](https://kubestash.com/docs/latest/concepts/crds/addon/) +- [Function](https://kubestash.com/docs/latest/concepts/crds/function/) + +To keep things isolated, we are going to use a separate namespace called `demo` throughout this tutorial. + +```bash +$ kubectl create ns demo +namespace/demo created +``` + +> **Note:** YAML files used in this tutorial are stored in [docs/guides/qdrant/backup/volumesnapshot/examples](docs/guides/qdrant/backup/volumesnapshot/examples) directory of [kubedb/docs](https://github.com/kubedb/docs) repository. + +## Prepare Backup Infrastructure + +We are going to store our backed up data using VolumeSnapshots. We have to create a `VolumeSnapshotClass`, `Secret`, `BackupStorage`, and `RetentionPolicy` CR to use this backend. If you want to use a different backend, please read the respective backend configuration doc from [here](https://kubestash.com/docs/latest/guides/backends/overview/). + +### Ensure VolumeSnapshotClass + +First, ensure that the `VolumeSnapshotClass` for your storage provider is available. For Longhorn: + +```bash +$ kubectl get volumesnapshotclasses +NAME DRIVER DELETIONPOLICY AGE +longhorn-snapshot-vsc driver.longhorn.io Delete 7d22h +``` + +If not available, create one: + +```yaml +apiVersion: snapshot.storage.k8s.io/v1 +kind: VolumeSnapshotClass +metadata: + name: longhorn-snapshot-vsc +driver: driver.longhorn.io +deletionPolicy: Delete +parameters: + type: snap +``` + +```bash +$ kubectl apply -f https://github.com/kubedb/docs/raw/{{< param "info.version" >}}/docs/guides/qdrant/backup/volumesnapshot/examples/volumesnapshotclass.yaml +volumesnapshotclass.snapshot.storage.k8s.io/longhorn-snapshot-vsc created +``` + +Note: Ensure that the VolumeSnapshotClass is provisioned with the same storage class driver used for provisioning your Qdrant database. + +### Create BackupStorage + +Create a `BackupStorage` CR to configure the backup storage: + +```yaml +apiVersion: storage.kubestash.com/v1alpha1 +kind: BackupStorage +metadata: + name: minio-storage + namespace: demo +spec: + storage: + provider: s3 + s3: + bucket: qdrant-backups + endpoint: http://minio.demo.svc:9000 + insecureTLS: true + prefix: backup/demo + region: us-east-1 + secretName: aws-secret + usagePolicy: + allowedNamespaces: + from: All + default: true + deletionPolicy: Delete +``` + +Apply the BackupStorage: + +```bash +$ kubectl apply -f https://github.com/kubedb/docs/raw/{{< param "info.version" >}}/docs/guides/qdrant/backup/volumesnapshot/examples/backupstorage.yaml +backupstorage.storage.kubestash.com/minio-storage created +``` + +### Create Storage Secret + +Create a secret with credentials to access the storage: + +```bash +$ kubectl apply -f https://github.com/kubedb/docs/raw/{{< param "info.version" >}}/docs/guides/qdrant/backup/volumesnapshot/examples/aws-secret.yaml +secret/aws-secret created +``` + +### Create Encryption Secret + +Create a secret for encrypting the backup data: + +```bash +$ echo -n 'changeit' > RESTIC_PASSWORD +$ kubectl create secret generic -n demo encrypt-secret \ + --from-file=./RESTIC_PASSWORD +secret "encrypt-secret" created +``` + +### Create RetentionPolicy + +Now, let's create a `RetentionPolicy` to specify how the old Snapshots should be cleaned up. + +Below is the YAML of the `RetentionPolicy` object that we are going to create, + +```yaml +apiVersion: storage.kubestash.com/v1alpha1 +kind: RetentionPolicy +metadata: + name: demo-retention + namespace: demo +spec: + default: true + maxNumberOfSnapshots: 5 + usagePolicy: + allowedNamespaces: + from: All +``` + +Let's create the above `RetentionPolicy`, + +```bash +$ kubectl apply -f https://github.com/kubedb/docs/raw/{{< param "info.version" >}}/docs/guides/qdrant/backup/volumesnapshot/examples/retentionpolicy.yaml +retentionpolicy.storage.kubestash.com/demo-retention created +``` + +## Deploy Sample Qdrant Database + +Let's deploy a sample `Qdrant` database and insert some data into it. + +**Create Qdrant CR:** + +Below is the YAML of a sample `Qdrant` CRD that we are going to create for this tutorial: + +```yaml +apiVersion: kubedb.com/v1 +kind: Qdrant +metadata: + name: sample-qdrant + namespace: demo +spec: + version: "1.17.0" + mode: Distributed + replicas: 3 + storage: + storageClassName: longhorn + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 200Mi + deletionPolicy: WipeOut +``` + +Create the above `Qdrant` CR, + +```bash +$ kubectl apply -f https://github.com/kubedb/docs/raw/{{< param "info.version" >}}/docs/guides/qdrant/backup/volumesnapshot/examples/sample-qdrant.yaml +qdrant.kubedb.com/sample-qdrant created +``` + +KubeDB will deploy a Qdrant database according to the above specification. + +Let's check if the database is ready to use, + +```bash +$ kubectl get qdrant -n demo +NAME VERSION STATUS AGE +sample-qdrant 1.17.0 Ready 4m22s +``` + +**Insert Sample Data:** + +Now, we are going to exec into the database pod and create some sample data. At first, find out the database `Pod` using the following command, + +```bash +$ kubectl get pods -n demo --selector="app.kubernetes.io/instance=sample-qdrant" +NAME READY STATUS RESTARTS AGE +sample-qdrant-0 1/1 Running 0 2m41s +sample-qdrant-1 1/1 Running 0 2m35s +sample-qdrant-2 1/1 Running 0 2m29s +``` + +Now, let's exec into the Pod to insert some sample data into Qdrant: + +```bash +$ kubectl exec -it -n demo sample-qdrant-0 -- sh +# Upload some sample points to a collection +$ wget -qO- --header 'Content-Type: application/json' \ + --post-data '{ + "vectors": [ + {"id": 1, "vector": [0.1, 0.2, 0.3, 0.4]}, + {"id": 2, "vector": [0.5, 0.6, 0.7, 0.8]} + ] + }' \ + http://localhost:6333/collections/my_collection/points +# Exit the pod +$ exit +``` + +Now, we are ready to backup the database. + +## Backup + +We have to create a `BackupConfiguration` targeting respective `sample-qdrant` Qdrant database. Then, KubeStash will create a `CronJob` for each session to take periodic backup of that database using volume snapshots. + +**Create BackupConfiguration:** + +Below is the YAML for `BackupConfiguration` CR to backup the `sample-qdrant` database that we have deployed earlier, + +```yaml +apiVersion: core.kubestash.com/v1alpha1 +kind: BackupConfiguration +metadata: + name: sample-qdrant-backup + namespace: demo +spec: + target: + apiGroup: kubedb.com + kind: Qdrant + namespace: demo + name: sample-qdrant + backends: + - name: minio-backend + storageRef: + namespace: demo + name: minio-storage + retentionPolicy: + name: demo-retention + namespace: demo + sessions: + - name: frequent-backup + scheduler: + schedule: "*/5 * * * *" + jobTemplate: + backoffLimit: 1 + repositories: + - name: minio-qdrant-repo + backend: minio-backend + directory: /qdrant + encryptionSecret: + name: encrypt-secret + namespace: demo + addon: + name: qdrant-addon + tasks: + - name: volume-snapshot + params: + volumeSnapshotClassName: "longhorn-snapshot-vsc" +``` + +Let's create the `BackupConfiguration` CR that we have shown above, + +```bash +$ kubectl apply -f https://github.com/kubedb/docs/raw/{{< param "info.version" >}}/docs/guides/qdrant/backup/volumesnapshot/examples/backupconfiguration.yaml +backupconfiguration.core.kubestash.com/sample-qdrant-backup created +``` + +**Verify Backup Setup Successful:** + +If everything goes well, the phase of the `BackupConfiguration` should be `Ready`. The `Ready` phase indicates that the backup setup is successful. Let's verify the `Phase` of the BackupConfiguration, + +```bash +$ kubectl get backupconfiguration -n demo +NAME PHASE PAUSED AGE +sample-qdrant-backup Ready 2m50s +``` + +Additionally, we can verify that the `Repository` specified in the `BackupConfiguration` has been created using the following command, + +```bash +$ kubectl get repo -n demo +NAME INTEGRITY SNAPSHOT-COUNT SIZE PHASE LAST-SUCCESSFUL-BACKUP AGE +minio-qdrant-repo 0 0 B Ready 3m +``` + +**Verify VolumeSnapshot:** + +It will create a `VolumeSnapshot` for each PVC of the Qdrant database. + +Verify that the `VolumeSnapshot` has been created using the following command, + +```bash +$ kubectl get volumesnapshot -n demo +NAME READYTOUSE SOURCEPVC SOURCESNAPSHOTCONTENT RESTORESIZE SNAPSHOTCLASS SNAPSHOTCONTENT CREATIONTIME AGE +minio-qdrant-repo-xyz true data-sample-qdrant-0 1Gi longhorn-snapshot-vsc snapcontent-xyz 2m 2m +``` + +**Verify BackupSession:** + +KubeStash triggers an instant backup as soon as the `BackupConfiguration` is ready. After that, backups are scheduled according to the specified schedule. + +```bash +$ kubectl get backupsession -n demo -w + +NAME INVOKER-TYPE INVOKER-NAME PHASE DURATION AGE +sample-qdrant-backup-frequent-backup-xyz BackupConfiguration sample-qdrant-backup Succeeded 7m22s +``` + +We can see from the above output that the backup session has succeeded. + +## Restore + +In this section, we are going to restore the database from the volume snapshot backup we have taken in the previous section. We are going to deploy a new database and initialize it from the backup. + +#### Deploy Restored Database: + +Now, we have to deploy the restored database similarly as we have deployed the original `sample-qdrant` database. However, this time there will be the following differences: + +- We are going to specify `.spec.init.waitForInitialRestore` field that tells KubeDB to wait for first restore to complete before marking this database is ready to use. + +Below is the YAML for `Qdrant` CRD we are going deploy to initialize from backup, + +```yaml +apiVersion: kubedb.com/v1 +kind: Qdrant +metadata: + name: restored-qdrant + namespace: demo +spec: + init: + waitForInitialRestore: true + version: "1.17.0" + mode: Distributed + replicas: 3 + storage: + storageClassName: longhorn + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 200Mi + deletionPolicy: WipeOut +``` + +Let's create the above database, + +```bash +$ kubectl apply -f https://github.com/kubedb/docs/raw/{{< param "info.version" >}}/docs/guides/qdrant/backup/volumesnapshot/examples/restored-qdrant.yaml +qdrant.kubedb.com/restored-qdrant created +``` + +If you check the database status, you will see it is stuck in `Provisioning` state. + +```bash +$ kubectl get qdrant -n demo restored-qdrant +NAME VERSION STATUS AGE +restored-qdrant 1.17.0 Provisioning 61s +``` + +#### Create RestoreSession: + +Now, we need to create a RestoreSession CRD pointing to targeted `Qdrant` database. + +Below, is the contents of YAML file of the `RestoreSession` object that we are going to create to restore backed up data into the newly created database provisioned by Qdrant object named `restored-qdrant`. + +```yaml +apiVersion: core.kubestash.com/v1alpha1 +kind: RestoreSession +metadata: + name: restore-sample-qdrant + namespace: demo +spec: + target: + apiGroup: kubedb.com + kind: Qdrant + namespace: demo + name: restored-qdrant + dataSource: + repository: minio-qdrant-repo + snapshot: latest + encryptionSecret: + name: encrypt-secret + namespace: demo + addon: + name: qdrant-addon + tasks: + - name: volume-snapshot-restore +``` + +Let's create the RestoreSession CRD object we have shown above, + +```bash +$ kubectl apply -f https://github.com/kubedb/docs/raw/{{< param "info.version" >}}/docs/guides/qdrant/backup/volumesnapshot/examples/restoresession.yaml +restoresession.core.kubestash.com/restore-sample-qdrant created +``` + +Once, you have created the `RestoreSession` object, KubeStash will create restore Job. Run the following command to watch the phase of the `RestoreSession` object, + +```bash +$ watch kubectl get restoresession -n demo +Every 2.0s: kubectl get restores... AppsCode-PC-03: Wed Aug 21 10:44:05 2024 + +NAME REPOSITORY FAILURE-POLICY PHASE DURATION AGE +restore-sample-qdrant minio-qdrant-repo Succeeded 3s 53s +``` + +The `Succeeded` phase means that the restore process has been completed successfully. + +#### Verify Restored Data: + +In this section, we are going to verify whether the desired data has been restored successfully. We are going to connect to the database server and check whether the collection we created earlier in the original database are restored. + +At first, check if the database has gone into `Ready` state by the following command, + +```bash +$ kubectl get qdrant -n demo restored-qdrant +NAME VERSION STATUS AGE +restored-qdrant 1.17.0 Ready 34m +``` + +Now, find out the database `Pod` by the following command, + +```bash +$ kubectl get pods -n demo --selector="app.kubernetes.io/instance=restored-qdrant" +NAME READY STATUS RESTARTS AGE +restored-qdrant-0 1/1 Running 0 39m +``` + +Now, let's exec into the Pod to enter into Qdrant and verify restored data, + +```bash +$ kubectl exec -it -n demo restored-qdrant-0 -- sh +# Check if the collection exists and has data +$ wget -qO- http://localhost:6333/collections/my_collection +# Exit the pod +$ exit +``` + +So, from the above output, we can see that the `my_collection` collection we created earlier in the original database and now, it is restored successfully. + +## Cleanup + +To cleanup the Kubernetes resources created by this tutorial, run: + +```bash +kubectl delete backupconfigurations.core.kubestash.com -n demo sample-qdrant-backup +kubectl delete restoresessions.core.kubestash.com -n demo restore-sample-qdrant +kubectl delete retentionpolicies.storage.kubestash.com -n demo demo-retention +kubectl delete backupstorage -n demo minio-storage +kubectl delete secret -n demo aws-secret +kubectl delete secret -n demo encrypt-secret +kubectl delete qdrant -n demo restored-qdrant +kubectl delete qdrant -n demo sample-qdrant +``` diff --git a/docs/guides/qdrant/configuration/_index.md b/docs/guides/qdrant/configuration/_index.md index 8d80a49d6..0e8253456 100644 --- a/docs/guides/qdrant/configuration/_index.md +++ b/docs/guides/qdrant/configuration/_index.md @@ -5,6 +5,6 @@ menu: identifier: qdrant-configuration name: Custom Configuration parent: qdrant-guides - weight: 30 + weight: 40 menu_name: docs_{{ .version }} --- \ No newline at end of file diff --git a/docs/guides/qdrant/distributed-deployment/_index.md b/docs/guides/qdrant/distributed-deployment/_index.md new file mode 100644 index 000000000..ba06aa7da --- /dev/null +++ b/docs/guides/qdrant/distributed-deployment/_index.md @@ -0,0 +1,10 @@ +--- +title: Distributed Deployment +menu: + docs_{{ .version }}: + identifier: qdrant-distributed-deployment + name: Distributed Deployment + parent: qdrant-guides + weight: 25 +menu_name: docs_{{ .version }} +--- diff --git a/docs/guides/qdrant/distributed-deployment/overview.md b/docs/guides/qdrant/distributed-deployment/overview.md new file mode 100644 index 000000000..e6943b1f4 --- /dev/null +++ b/docs/guides/qdrant/distributed-deployment/overview.md @@ -0,0 +1,187 @@ +--- +title: Distributed Deployment +menu: + docs_{{ .version }}: + identifier: qdrant-distributed-deployment-overview + name: Overview + parent: qdrant-distributed-deployment + weight: 10 +menu_name: docs_{{ .version }} +section_menu_id: guides +--- + +> New to KubeDB? Please start [here](/docs/README.md). + +# Qdrant Distributed Deployment + +This tutorial will show you how to deploy a Qdrant database in distributed mode using KubeDB. + +## Before You Begin + +At first, you need to have a Kubernetes cluster, and the `kubectl` command-line tool must be configured to communicate with your cluster. If you do not already have a cluster, you can create one by using [kind](https://kind.sigs.k8s.io/docs/user/quick-start/). + +Now, install KubeDB cli on your workstation and KubeDB operator in your cluster following the steps [here](/docs/setup/README.md). + +To keep things isolated, this tutorial uses a separate namespace called `demo` throughout this tutorial. + +```bash +$ kubectl create ns demo +namespace/demo created +``` + +> **Note:** YAML files used in this tutorial are stored in [docs/examples/qdrant/quickstart](/docs/examples/qdrant/quickstart) directory of [kubedb/docs](https://github.com/kubedb/docs) repository. + +## Find Available StorageClass + +We will need to provide `StorageClass` in the Qdrant CR specification. Check available `StorageClass` in your cluster using the following command: + +```bash +$ kubectl get storageclass +NAME PROVISIONER RECLAIMPOLICY VOLUMEBINDINGMODE ALLOWVOLUMEEXPANSION AGE +standard (default) rancher.io/local-path Delete WaitForFirstConsumer false 10d +``` + +Here, we have `standard` StorageClass in our cluster. + +## Find Available QdrantVersion + +When you install KubeDB, it creates `QdrantVersion` CRDs for all supported Qdrant versions. Let's check available `QdrantVersion`s: + +```bash +$ kubectl get qdrantversions +NAME VERSION DB_IMAGE DEPRECATED AGE +1.17.0 1.17.0 docker.io/qdrant/qdrant:v1.17.0-unprivileged 13d +``` + +In this tutorial, we will use `1.17.0` QdrantVersion CR to create a distributed Qdrant cluster. + +## Deploy Distributed Qdrant + +KubeDB implements a `Qdrant` CRD to define the specification of a Qdrant database. For distributed deployment, you need to set `spec.mode` to `Distributed` and specify the number of replicas. + +Below is the `Qdrant` object created in this tutorial: + +```yaml +apiVersion: kubedb.com/v1alpha2 +kind: Qdrant +metadata: + name: qdrant-sample + namespace: demo +spec: + version: 1.17.0 + mode: Distributed + replicas: 3 + storage: + storageClassName: longhorn + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 200Mi + podTemplate: + spec: + containers: + - name: qdrant + resources: + requests: + cpu: 100m + memory: 100Mi + limits: + cpu: 100m + memory: 100Mi + deletionPolicy: WipeOut +``` + +Here, +- `spec.version` specifies the version of Qdrant to use +- `spec.mode` set to `Distributed` enables distributed mode +- `spec.replicas` specifies the number of Qdrant nodes (default is 1) +- `spec.storage` specifies the storage configuration for each node +- `spec.podTemplate` allows setting resource limits and requests + +Let's create the Qdrant object: + +```bash +$ kubectl create -f https://github.com/kubedb/docs/raw/{{< param "info.version" >}}/docs/guides/qdrant/distributed-deployment/yamls/qdrant-distributed.yaml +qdrant.kubedb.com/qdrant-sample created +``` + +## Verify the Deployment + +Let's check the status of the Qdrant object: + +```bash +$ kubectl get qdrant -n demo +NAME VERSION STATUS AGE +qdrant-sample 1.17.0 Ready 2m +``` + +To see the distributed nodes, check the pods: + +```bash +$ kubectl get pods -n demo -l app.kubernetes.io/instance=qdrant-sample +NAME READY STATUS RESTARTS AGE +qdrant-sample-0 1/1 Running 0 2m +qdrant-sample-1 1/1 Running 0 2m +qdrant-sample-2 1/1 Running 0 2m +``` + +In distributed mode, Qdrant creates a StatefulSet with the specified number of replicas. + +## Horizontal Scaling + +You can scale the Qdrant cluster horizontally by increasing or decreasing the number of replicas using `QdrantOpsRequest`. + +To scale up to 5 nodes: + +```yaml +apiVersion: ops.kubedb.com/v1alpha1 +kind: QdrantOpsRequest +metadata: + name: qdrant-hor-scaling + namespace: demo +spec: + type: HorizontalScaling + databaseRef: + name: qdrant-sample + horizontalScaling: + node: 5 +``` + +Apply the scaling request: + +```bash +$ kubectl create -f https://github.com/kubedb/docs/raw/{{< param "info.version" >}}/docs/guides/qdrant/distributed-deployment/yamls/qdrant-hor-scaling.yaml +qdrantopsrequest.ops.kubedb.com/qdrant-hor-scaling created +``` + +Monitor the scaling operation: + +```bash +$ kubectl get qdrantopsrequest -n demo +NAME TYPE PHASE AGE +qdrant-hor-scaling HorizontalScaling Done 2m +``` + +Verify the new replica count: + +```bash +$ kubectl get pods -n demo -l app.kubernetes.io/instance=qdrant-sample +NAME READY STATUS RESTARTS AGE +qdrant-sample-0 1/1 Running 0 5m +qdrant-sample-1 1/1 Running 0 5m +qdrant-sample-2 1/1 Running 0 5m +qdrant-sample-3 1/1 Running 0 2m +qdrant-sample-4 1/1 Running 0 2m +``` + +## Cleaning Up + +To delete the Qdrant database and all associated resources: + +```bash +$ kubectl delete qdrant -n demo qdrant-sample +qdrant.kubedb.com "qdrant-sample" deleted +``` + +> **Warning:** If you delete the Qdrant object with `deletionPolicy: WipeOut`, all data will be permanently deleted. diff --git a/docs/guides/qdrant/monitoring/_index.md b/docs/guides/qdrant/monitoring/_index.md index a7545f31d..502f59cd0 100644 --- a/docs/guides/qdrant/monitoring/_index.md +++ b/docs/guides/qdrant/monitoring/_index.md @@ -5,6 +5,6 @@ menu: identifier: qdrant-monitoring name: Monitoring parent: qdrant-guides - weight: 140 + weight: 150 menu_name: docs_{{ .version }} --- diff --git a/docs/guides/qdrant/quickstart/quickstart.md b/docs/guides/qdrant/quickstart/quickstart.md index 0abfd38a4..6b4d7912c 100644 --- a/docs/guides/qdrant/quickstart/quickstart.md +++ b/docs/guides/qdrant/quickstart/quickstart.md @@ -49,17 +49,11 @@ When you install KubeDB, it creates `QdrantVersion` CRDs for all supported Qdran ```bash $ kubectl get qdrantversions -NAME VERSION DB_IMAGE DEPRECATED AGE -1.7.4 1.7.4 qdrant/qdrant:v1.7.4 3d -1.8.4 1.8.4 qdrant/qdrant:v1.8.4 3d -1.10.0 1.10.0 qdrant/qdrant:v1.10.0 3d -1.11.0 1.11.0 qdrant/qdrant:v1.11.0 3d -1.12.0 1.12.0 qdrant/qdrant:v1.12.0 3d -1.13.0 1.13.0 qdrant/qdrant:v1.13.0 3d -1.14.0 1.14.0 qdrant/qdrant:v1.14.0 3d -1.15.0 1.15.0 qdrant/qdrant:v1.15.0 3d -1.16.0 1.16.0 qdrant/qdrant:v1.16.0 3d -1.17.0 1.17.0 qdrant/qdrant:v1.17.0 3d +NAME VERSION DB_IMAGE DEPRECATED AGE +1.15.4 1.15.4 docker.io/qdrant/qdrant:v1.15.4-unprivileged 13d +1.16.2 1.16.2 docker.io/qdrant/qdrant:v1.16.2-unprivileged 13d +1.17.0 1.17.0 docker.io/qdrant/qdrant:v1.17.0-unprivileged 13d + ``` Notice the `DEPRECATED` column. `true` means that QdrantVersion is deprecated for the current KubeDB version and KubeDB will not work for that version. @@ -80,7 +74,7 @@ metadata: namespace: demo spec: version: "1.17.0" - replicas: 3 + mode: "standalone" storage: storageClassName: "standard" accessModes: diff --git a/docs/guides/qdrant/reconfigure-tls/_index.md b/docs/guides/qdrant/reconfigure-tls/_index.md index 8c870befc..5c8936173 100644 --- a/docs/guides/qdrant/reconfigure-tls/_index.md +++ b/docs/guides/qdrant/reconfigure-tls/_index.md @@ -5,6 +5,6 @@ menu: identifier: qdrant-reconfigure-tls name: Reconfigure TLS parent: qdrant-guides - weight: 70 + weight: 80 menu_name: docs_{{ .version }} --- diff --git a/docs/guides/qdrant/reconfigure/_index.md b/docs/guides/qdrant/reconfigure/_index.md index 0e41ac5f6..b6f1276ca 100644 --- a/docs/guides/qdrant/reconfigure/_index.md +++ b/docs/guides/qdrant/reconfigure/_index.md @@ -5,6 +5,6 @@ menu: identifier: qdrant-reconfigure name: Reconfigure parent: qdrant-guides - weight: 60 + weight: 70 menu_name: docs_{{ .version }} --- diff --git a/docs/guides/qdrant/restart/_index.md b/docs/guides/qdrant/restart/_index.md index 81fb1f1f8..c649fe0bf 100644 --- a/docs/guides/qdrant/restart/_index.md +++ b/docs/guides/qdrant/restart/_index.md @@ -5,6 +5,6 @@ menu: identifier: qdrant-restart name: Restart parent: qdrant-guides - weight: 80 + weight: 90 menu_name: docs_{{ .version }} --- diff --git a/docs/guides/qdrant/rotate-auth/_index.md b/docs/guides/qdrant/rotate-auth/_index.md index 53839e13c..031524a4a 100644 --- a/docs/guides/qdrant/rotate-auth/_index.md +++ b/docs/guides/qdrant/rotate-auth/_index.md @@ -5,6 +5,6 @@ menu: identifier: qdrant-rotate-auth name: Rotate Auth parent: qdrant-guides - weight: 90 + weight: 100 menu_name: docs_{{ .version }} --- diff --git a/docs/guides/qdrant/scaling/_index.md b/docs/guides/qdrant/scaling/_index.md index 70ca1f624..7584e95ea 100644 --- a/docs/guides/qdrant/scaling/_index.md +++ b/docs/guides/qdrant/scaling/_index.md @@ -5,6 +5,6 @@ menu: identifier: qdrant-scaling name: Scaling parent: qdrant-guides - weight: 120 + weight: 130 menu_name: docs_{{ .version }} --- diff --git a/docs/guides/qdrant/tls/_index.md b/docs/guides/qdrant/tls/_index.md index 077ea4ee2..608687efa 100644 --- a/docs/guides/qdrant/tls/_index.md +++ b/docs/guides/qdrant/tls/_index.md @@ -5,6 +5,6 @@ menu: identifier: qdrant-tls name: TLS parent: qdrant-guides - weight: 40 + weight: 50 menu_name: docs_{{ .version }} --- diff --git a/docs/guides/qdrant/update-version/_index.md b/docs/guides/qdrant/update-version/_index.md index b2fe5a572..23ed313fe 100644 --- a/docs/guides/qdrant/update-version/_index.md +++ b/docs/guides/qdrant/update-version/_index.md @@ -5,6 +5,6 @@ menu: identifier: qdrant-update-version name: Update Version parent: qdrant-guides - weight: 100 + weight: 110 menu_name: docs_{{ .version }} --- diff --git a/docs/guides/qdrant/volume-expansion/_index.md b/docs/guides/qdrant/volume-expansion/_index.md index 191ed04e0..578d4ec84 100644 --- a/docs/guides/qdrant/volume-expansion/_index.md +++ b/docs/guides/qdrant/volume-expansion/_index.md @@ -5,6 +5,6 @@ menu: identifier: qdrant-volume-expansion name: Volume Expansion parent: qdrant-guides - weight: 110 + weight: 120 menu_name: docs_{{ .version }} --- From e059df9b06d03ca63c214151c953fe2c3e51e012 Mon Sep 17 00:00:00 2001 From: Abdullah Al Masood <98589987+al-masood@users.noreply.github.com> Date: Mon, 4 May 2026 12:10:38 +0600 Subject: [PATCH 05/10] backup file name changes Signed-off-by: Abdullah Al Masood <98589987+al-masood@users.noreply.github.com> --- docs/guides/qdrant/README.md | 3 +- .../compute/{cluster.md => database.md} | 52 +++--- .../storage/{cluster.md => database.md} | 68 ++++---- .../logical/examples/backupconfiguration.yaml | 38 ++++ .../logical/examples/backupstorage.yaml | 20 +++ .../qdrant/backup/logical/examples/minio.yaml | 74 ++++++++ .../logical/examples/qdrant-restored.yaml | 19 ++ .../backup/logical/examples/qdrant.yaml | 17 ++ .../logical/examples/restoresession.yaml | 21 +++ .../logical/examples/retentionpolicy.yaml | 11 ++ .../logical/examples/storage-secret.yaml | 9 + docs/guides/qdrant/backup/logical/index.md | 18 +- .../examples/backupconfiguration.yaml | 38 ++++ .../examples/backupstorage.yaml | 20 +++ .../examples/qdrant-restored.yaml | 19 ++ .../volumesnapshot/examples/qdrant.yaml | 17 ++ .../examples/restoresession.yaml | 21 +++ .../examples/retentionpolicy.yaml | 11 ++ .../examples/storage-secret.yaml | 9 + .../examples/volumesnapshotclass.yaml | 8 + .../qdrant/backup/volumesnapshot/index.md | 8 +- docs/guides/qdrant/concepts/_index.md | 155 ++++++++++++++++- docs/guides/qdrant/concepts/autoscaler.md | 4 +- docs/guides/qdrant/concepts/qdrant.md | 164 ------------------ 24 files changed, 578 insertions(+), 246 deletions(-) rename docs/guides/qdrant/autoscaler/compute/{cluster.md => database.md} (78%) rename docs/guides/qdrant/autoscaler/storage/{cluster.md => database.md} (74%) create mode 100644 docs/guides/qdrant/backup/logical/examples/backupconfiguration.yaml create mode 100644 docs/guides/qdrant/backup/logical/examples/backupstorage.yaml create mode 100644 docs/guides/qdrant/backup/logical/examples/minio.yaml create mode 100644 docs/guides/qdrant/backup/logical/examples/qdrant-restored.yaml create mode 100644 docs/guides/qdrant/backup/logical/examples/qdrant.yaml create mode 100644 docs/guides/qdrant/backup/logical/examples/restoresession.yaml create mode 100644 docs/guides/qdrant/backup/logical/examples/retentionpolicy.yaml create mode 100644 docs/guides/qdrant/backup/logical/examples/storage-secret.yaml create mode 100644 docs/guides/qdrant/backup/volumesnapshot/examples/backupconfiguration.yaml create mode 100644 docs/guides/qdrant/backup/volumesnapshot/examples/backupstorage.yaml create mode 100644 docs/guides/qdrant/backup/volumesnapshot/examples/qdrant-restored.yaml create mode 100644 docs/guides/qdrant/backup/volumesnapshot/examples/qdrant.yaml create mode 100644 docs/guides/qdrant/backup/volumesnapshot/examples/restoresession.yaml create mode 100644 docs/guides/qdrant/backup/volumesnapshot/examples/retentionpolicy.yaml create mode 100644 docs/guides/qdrant/backup/volumesnapshot/examples/storage-secret.yaml create mode 100644 docs/guides/qdrant/backup/volumesnapshot/examples/volumesnapshotclass.yaml delete mode 100644 docs/guides/qdrant/concepts/qdrant.md diff --git a/docs/guides/qdrant/README.md b/docs/guides/qdrant/README.md index 1313aabe4..1c0cb4d6d 100644 --- a/docs/guides/qdrant/README.md +++ b/docs/guides/qdrant/README.md @@ -43,5 +43,6 @@ KubeDB supports Qdrant vector databases through the `Qdrant` CRD. ## User Guide - [Quickstart Qdrant](/docs/guides/qdrant/quickstart/quickstart.md) with KubeDB operator. -- Detail concepts of [Qdrant Object](/docs/guides/qdrant/concepts/qdrant.md). +- Deploy [Distributed Qdrant](/docs/guides/qdrant/distributed-deployment/overview.md) cluster. +- Detail concepts of [Qdrant Object](/docs/guides/qdrant/concepts/). - Want to hack on KubeDB? Check our [contribution guidelines](/docs/CONTRIBUTING.md). diff --git a/docs/guides/qdrant/autoscaler/compute/cluster.md b/docs/guides/qdrant/autoscaler/compute/database.md similarity index 78% rename from docs/guides/qdrant/autoscaler/compute/cluster.md rename to docs/guides/qdrant/autoscaler/compute/database.md index 14a8ef357..589935c5d 100644 --- a/docs/guides/qdrant/autoscaler/compute/cluster.md +++ b/docs/guides/qdrant/autoscaler/compute/database.md @@ -1,9 +1,9 @@ --- -title: Qdrant Compute Autoscaler Cluster +title: Qdrant Compute Autoscaler menu: docs_{{ .version }}: - identifier: qdrant-autoscaler-compute-cluster - name: Cluster + identifier: qdrant-autoscaler-compute-database + name: Database parent: qdrant-autoscaler-compute weight: 20 menu_name: docs_{{ .version }} @@ -12,9 +12,9 @@ section_menu_id: guides > New to KubeDB? Please start [here](/docs/README.md). -# Autoscaling the Compute Resource of a Qdrant Cluster +# Autoscaling the Compute Resource of a Qdrant Database -This guide will show you how to use `KubeDB` to auto-scale compute resources i.e. CPU and memory of a Qdrant cluster database. +This guide will show you how to use `KubeDB` to auto-scale compute resources i.e. CPU and memory of a Qdrant database. ## Before You Begin @@ -25,7 +25,7 @@ This guide will show you how to use `KubeDB` to auto-scale compute resources i.e - Install `Metrics Server` from [here](https://github.com/kubernetes-sigs/metrics-server#installation). - You should be familiar with the following `KubeDB` concepts: - - [Qdrant](/docs/guides/qdrant/concepts/qdrant.md) + - [Qdrant](/docs/guides/qdrant/concepts/) - [QdrantOpsRequest](/docs/guides/qdrant/concepts/opsrequest.md) - [Compute Resource Autoscaling Overview](/docs/guides/qdrant/autoscaler/overview.md) @@ -36,19 +36,19 @@ $ kubectl create ns demo namespace/demo created ``` -## Autoscaling of Cluster Database +## Autoscaling of Database -Here, we are going to deploy a `Qdrant` cluster using a supported version by `KubeDB` operator. Then we are going to apply `QdrantAutoscaler` to set up autoscaling. +Here, we are going to deploy a `Qdrant` database using a supported version by `KubeDB` operator. Then we are going to apply `QdrantAutoscaler` to set up autoscaling. -### Deploy Qdrant Cluster +### Deploy Qdrant Database -In this section, we are going to deploy a Qdrant cluster with version `1.17.0`. Then, in the next section we will set up autoscaling for this database using `QdrantAutoscaler` CRD. Below is the YAML of the `Qdrant` CR that we are going to create: +In this section, we are going to deploy a Qdrant database with version `1.17.0`. Then, in the next section we will set up autoscaling for this database using `QdrantAutoscaler` CRD. Below is the YAML of the `Qdrant` CR that we are going to create: ```yaml apiVersion: kubedb.com/v1alpha2 kind: Qdrant metadata: - name: qdrant-cluster + name: qdrant-db namespace: demo spec: version: "1.17.0" @@ -78,22 +78,22 @@ spec: Let's create the `Qdrant` CR we have shown above: ```bash -$ kubectl apply -f https://github.com/kubedb/docs/raw/{{< param "info.version" >}}/docs/examples/qdrant/autoscaler/compute/qdrant-cluster.yaml -qdrant.kubedb.com/qdrant-cluster created +$ kubectl apply -f https://github.com/kubedb/docs/raw/{{< param "info.version" >}}/docs/examples/qdrant/autoscaler/compute/qdrant-db.yaml +qdrant.kubedb.com/qdrant-db created ``` -Now, wait until `qdrant-cluster` has status `Ready`: +Now, wait until `qdrant-db` has status `Ready`: ```bash $ kubectl get qdrant -n demo NAME VERSION STATUS AGE -qdrant-cluster 1.17.0 Ready 4m +qdrant-db 1.17.0 Ready 4m ``` Let's check the Pod container resources: ```bash -$ kubectl get pod -n demo qdrant-cluster-0 -o json | jq '.spec.containers[].resources' +$ kubectl get pod -n demo qdrant-db-0 -o json | jq '.spec.containers[].resources' { "limits": { "cpu": "200m", @@ -114,7 +114,7 @@ Here, we are going to set up compute resource autoscaling using a `QdrantAutosca #### Create QdrantAutoscaler Object -In order to set up compute resource autoscaling for this database cluster, we have to create a `QdrantAutoscaler` CR with our desired configuration. Below is the YAML of the `QdrantAutoscaler` object that we are going to create: +In order to set up compute resource autoscaling for this database, we have to create a `QdrantAutoscaler` CR with our desired configuration. Below is the YAML of the `QdrantAutoscaler` object that we are going to create: ```yaml apiVersion: autoscaling.kubedb.com/v1alpha1 @@ -124,7 +124,7 @@ metadata: namespace: demo spec: databaseRef: - name: qdrant-cluster + name: qdrant-db opsRequestOptions: timeout: 3m apply: IfReady @@ -145,7 +145,7 @@ spec: Here, -- `spec.databaseRef.name` specifies that we are performing compute autoscaling on `qdrant-cluster` database. +- `spec.databaseRef.name` specifies that we are performing compute autoscaling on `qdrant-db` database. - `spec.compute.node.trigger` specifies that compute resource autoscaling is enabled for the Qdrant nodes. - `spec.compute.node.podLifeTimeThreshold` specifies the minimum age of a Pod before the `VerticalPodAutoscaler` can recommend a resource update. - `spec.compute.node.resourceDiffPercentage` specifies the minimum percentage change needed before applying a new resource recommendation. @@ -194,27 +194,27 @@ Spec: Resource Diff Percentage: 20 Trigger: On Database Ref: - Name: qdrant-cluster + Name: qdrant-db Ops Request Options: Apply: IfReady Timeout: 3m0s Events: ``` -So, the `QdrantAutoscaler` resource is created successfully. The operator will now watch the resource usage of the Qdrant pods and create `QdrantOpsRequest` resources to scale the cluster when needed. +So, the `QdrantAutoscaler` resource is created successfully. The operator will now watch the resource usage of the Qdrant pods and create `QdrantOpsRequest` resources to scale when needed. After some time, you can observe that the autoscaler has created a `QdrantOpsRequest` with type `VerticalScaling`: ```bash $ kubectl get qdrantopsrequest -n demo NAME TYPE STATUS AGE -qdops-qdrant-cluster-xxxxxxxx VerticalScaling Successful 5m +qdops-qdrant-db-xxxxxxxx VerticalScaling Successful 5m ``` You can then verify the updated resources on the pods: ```bash -$ kubectl get pod -n demo qdrant-cluster-0 -o json | jq '.spec.containers[].resources' +$ kubectl get pod -n demo qdrant-db-0 -o json | jq '.spec.containers[].resources' { "limits": { "cpu": "400m", @@ -227,14 +227,14 @@ $ kubectl get pod -n demo qdrant-cluster-0 -o json | jq '.spec.containers[].reso } ``` -The above output verifies that we have successfully autoscaled the resources of the Qdrant cluster database. +The above output verifies that we have successfully autoscaled the resources of the Qdrant database. ## Cleaning Up To clean up the Kubernetes resources created by this tutorial, run: ```bash -kubectl delete qdrant -n demo qdrant-cluster +kubectl delete qdrant -n demo qdrant-db kubectl delete qdrantautoscaler -n demo qdrant-as-compute kubectl delete ns demo -``` \ No newline at end of file +``` diff --git a/docs/guides/qdrant/autoscaler/storage/cluster.md b/docs/guides/qdrant/autoscaler/storage/database.md similarity index 74% rename from docs/guides/qdrant/autoscaler/storage/cluster.md rename to docs/guides/qdrant/autoscaler/storage/database.md index f2321171a..2dd712261 100644 --- a/docs/guides/qdrant/autoscaler/storage/cluster.md +++ b/docs/guides/qdrant/autoscaler/storage/database.md @@ -1,9 +1,9 @@ --- -title: Qdrant Storage Autoscaler Cluster +title: Qdrant Storage Autoscaler menu: docs_{{ .version }}: - identifier: qdrant-autoscaler-storage-cluster - name: Cluster + identifier: qdrant-autoscaler-storage-database + name: Database parent: qdrant-autoscaler-storage weight: 20 menu_name: docs_{{ .version }} @@ -12,9 +12,9 @@ section_menu_id: guides > New to KubeDB? Please start [here](/docs/README.md). -# Storage Autoscaling of a Qdrant Cluster +# Storage Autoscaling of a Qdrant Database -This guide will show you how to use `KubeDB` to autoscale the storage of a Qdrant cluster database. +This guide will show you how to use `KubeDB` to autoscale the storage of a Qdrant database. ## Before You Begin @@ -29,7 +29,7 @@ This guide will show you how to use `KubeDB` to autoscale the storage of a Qdran - You must have a `StorageClass` that supports volume expansion. - You should be familiar with the following `KubeDB` concepts: - - [Qdrant](/docs/guides/qdrant/concepts/qdrant.md) + - [Qdrant](/docs/guides/qdrant/concepts/) - [QdrantOpsRequest](/docs/guides/qdrant/concepts/opsrequest.md) - [Storage Autoscaling Overview](/docs/guides/qdrant/autoscaler/overview.md) @@ -40,7 +40,7 @@ $ kubectl create ns demo namespace/demo created ``` -## Storage Autoscaling of Cluster Database +## Storage Autoscaling of Database At first, verify that your cluster has a storage class that supports volume expansion: @@ -53,17 +53,17 @@ topolvm-provisioner topolvm.cybozu.com Delete WaitForFirstConsum We can see from the output that `topolvm-provisioner` storage class has `ALLOWVOLUMEEXPANSION` set to `true`. We will use it for this tutorial. You can install topolvm from [here](https://github.com/topolvm/topolvm). -Now, we are going to deploy a `Qdrant` cluster using a supported version by `KubeDB` operator. Then we are going to apply `QdrantAutoscaler` to set up autoscaling. +Now, we are going to deploy a `Qdrant` database using a supported version by `KubeDB` operator. Then we are going to apply `QdrantAutoscaler` to set up autoscaling. -### Deploy Qdrant Cluster +### Deploy Qdrant Database -In this section, we are going to deploy a Qdrant cluster database with version `1.17.0`. Then, in the next section we will set up autoscaling for this database using `QdrantAutoscaler` CRD. Below is the YAML of the `Qdrant` CR that we are going to create: +In this section, we are going to deploy a Qdrant database with version `1.17.0`. Then, in the next section we will set up autoscaling for this database using `QdrantAutoscaler` CRD. Below is the YAML of the `Qdrant` CR that we are going to create: ```yaml apiVersion: kubedb.com/v1alpha2 kind: Qdrant metadata: - name: qdrant-cluster + name: qdrant-db namespace: demo spec: version: "1.17.0" @@ -82,29 +82,29 @@ spec: Let's create the `Qdrant` CR we have shown above: ```bash -$ kubectl apply -f https://github.com/kubedb/docs/raw/{{< param "info.version" >}}/docs/examples/qdrant/autoscaler/storage/qdrant-cluster.yaml -qdrant.kubedb.com/qdrant-cluster created +$ kubectl apply -f https://github.com/kubedb/docs/raw/{{< param "info.version" >}}/docs/examples/qdrant/autoscaler/storage/qdrant-db.yaml +qdrant.kubedb.com/qdrant-db created ``` -Now, wait until `qdrant-cluster` has status `Ready`: +Now, wait until `qdrant-db` has status `Ready`: ```bash $ kubectl get qdrant -n demo NAME VERSION STATUS AGE -qdrant-cluster 1.17.0 Ready 3m46s +qdrant-db 1.17.0 Ready 3m46s ``` Let's check the volume size from the StatefulSet and from the persistent volumes: ```bash -$ kubectl get sts -n demo qdrant-cluster -o json | jq '.spec.volumeClaimTemplates[].spec.resources.requests.storage' +$ kubectl get sts -n demo qdrant-db -o json | jq '.spec.volumeClaimTemplates[].spec.resources.requests.storage' "1Gi" $ kubectl get pv -n demo NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE -pvc-43266d76-f280-4cca-bd78-d13660a84db9 1Gi RWO Delete Bound demo/data-qdrant-cluster-2 topolvm-provisioner 57s -pvc-4a509b05-774b-42d9-b36d-599c9056af37 1Gi RWO Delete Bound demo/data-qdrant-cluster-0 topolvm-provisioner 58s -pvc-c27eee12-cd86-4410-b39e-b1dd735fc14d 1Gi RWO Delete Bound demo/data-qdrant-cluster-1 topolvm-provisioner 57s +pvc-43266d76-f280-4cca-bd78-d13660a84db9 1Gi RWO Delete Bound demo/data-qdrant-db-2 topolvm-provisioner 57s +pvc-4a509b05-774b-42d9-b36d-599c9056af37 1Gi RWO Delete Bound demo/data-qdrant-db-0 topolvm-provisioner 58s +pvc-c27eee12-cd86-4410-b39e-b1dd735fc14d 1Gi RWO Delete Bound demo/data-qdrant-db-1 topolvm-provisioner 57s ``` You can see the StatefulSet has 1GB storage and the capacity of all the persistent volumes is also 1GB. @@ -117,7 +117,7 @@ Here, we are going to set up storage autoscaling using a `QdrantAutoscaler` Obje #### Create QdrantAutoscaler Object -In order to set up storage autoscaling for this cluster database, we have to create a `QdrantAutoscaler` CR with our desired configuration. Below is the YAML of the `QdrantAutoscaler` object that we are going to create: +In order to set up storage autoscaling for this database, we have to create a `QdrantAutoscaler` CR with our desired configuration. Below is the YAML of the `QdrantAutoscaler` object that we are going to create: ```yaml apiVersion: autoscaling.kubedb.com/v1alpha1 @@ -127,7 +127,7 @@ metadata: namespace: demo spec: databaseRef: - name: qdrant-cluster + name: qdrant-db storage: node: trigger: "On" @@ -138,7 +138,7 @@ spec: Here, -- `spec.databaseRef.name` specifies that we are performing storage autoscaling on `qdrant-cluster` database. +- `spec.databaseRef.name` specifies that we are performing storage autoscaling on `qdrant-db` database. - `spec.storage.node.trigger` specifies that storage autoscaling is enabled for the Qdrant nodes. - `spec.storage.node.usageThreshold` specifies the storage usage threshold — if storage usage exceeds `20%`, storage autoscaling will be triggered. - `spec.storage.node.scalingThreshold` specifies the scaling threshold — storage will be scaled to `20%` of the current amount. @@ -169,7 +169,7 @@ API Version: autoscaling.kubedb.com/v1alpha1 Kind: QdrantAutoscaler Spec: Database Ref: - Name: qdrant-cluster + Name: qdrant-db Storage: Node: Expansion Mode: Online @@ -184,15 +184,15 @@ So, the `QdrantAutoscaler` resource is created successfully. The operator will n Now, for this demo, we are going to manually fill up the persistent volume to exceed the `usageThreshold` using the `dd` command to see if storage autoscaling is working: ```bash -$ kubectl exec -it -n demo qdrant-cluster-0 -- bash -root@qdrant-cluster-0:/qdrant/storage# df -h /qdrant/storage +$ kubectl exec -it -n demo qdrant-db-0 -- bash +root@qdrant-db-0:/qdrant/storage# df -h /qdrant/storage Filesystem Size Used Avail Use% Mounted on /dev/topolvm/57cd4330-784f-42c1-bf8e-e743241df164 1014M 32M 983M 4% /qdrant/storage -root@qdrant-cluster-0:/qdrant/storage# dd if=/dev/zero of=/qdrant/storage/file.img bs=800M count=1 +root@qdrant-db-0:/qdrant/storage# dd if=/dev/zero of=/qdrant/storage/file.img bs=800M count=1 1+0 records in 1+0 records out 838860800 bytes (839 MB, 800 MiB) copied, 6.47 s, 130 MB/s -root@qdrant-cluster-0:/qdrant/storage# df -h /qdrant/storage +root@qdrant-db-0:/qdrant/storage# df -h /qdrant/storage Filesystem Size Used Avail Use% Mounted on /dev/topolvm/57cd4330-784f-42c1-bf8e-e743241df164 1014M 832M 183M 82% /qdrant/storage ``` @@ -202,8 +202,8 @@ Now let's watch the `QdrantOpsRequest` in the demo namespace: ```bash $ kubectl get qdrantopsrequest -n demo -w NAME TYPE STATUS AGE -qdops-qdrant-cluster-xxxxxxxx VolumeExpansion Progressing 10s -qdops-qdrant-cluster-xxxxxxxx VolumeExpansion Successful 2m +qdops-qdrant-db-xxxxxxxx VolumeExpansion Progressing 10s +qdops-qdrant-db-xxxxxxxx VolumeExpansion Successful 2m ``` After the `QdrantOpsRequest` completes successfully, let's check the updated storage: @@ -211,9 +211,9 @@ After the `QdrantOpsRequest` completes successfully, let's check the updated sto ```bash $ kubectl get pv -n demo NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE -pvc-43266d76-f280-4cca-bd78-d13660a84db9 1217Mi RWO Delete Bound demo/data-qdrant-cluster-2 topolvm-provisioner 15m -pvc-4a509b05-774b-42d9-b36d-599c9056af37 1217Mi RWO Delete Bound demo/data-qdrant-cluster-0 topolvm-provisioner 15m -pvc-c27eee12-cd86-4410-b39e-b1dd735fc14d 1217Mi RWO Delete Bound demo/data-qdrant-cluster-1 topolvm-provisioner 15m +pvc-43266d76-f280-4cca-bd78-d13660a84db9 1217Mi RWO Delete Bound demo/data-qdrant-db-2 topolvm-provisioner 15m +pvc-4a509b05-774b-42d9-b36d-599c9056af37 1217Mi RWO Delete Bound demo/data-qdrant-db-0 topolvm-provisioner 15m +pvc-c27eee12-cd86-4410-b39e-b1dd735fc14d 1217Mi RWO Delete Bound demo/data-qdrant-db-1 topolvm-provisioner 15m ``` The storage has been automatically scaled from 1Gi to ~1.2Gi (120% of 1Gi) as we specified a `scalingThreshold` of 20%. @@ -223,7 +223,7 @@ The storage has been automatically scaled from 1Gi to ~1.2Gi (120% of 1Gi) as we To clean up the Kubernetes resources created by this tutorial, run: ```bash -kubectl delete qdrant -n demo qdrant-cluster +kubectl delete qdrant -n demo qdrant-db kubectl delete qdrantautoscaler -n demo qdrant-as-storage kubectl delete ns demo -``` \ No newline at end of file +``` diff --git a/docs/guides/qdrant/backup/logical/examples/backupconfiguration.yaml b/docs/guides/qdrant/backup/logical/examples/backupconfiguration.yaml new file mode 100644 index 000000000..27db6fb0c --- /dev/null +++ b/docs/guides/qdrant/backup/logical/examples/backupconfiguration.yaml @@ -0,0 +1,38 @@ +apiVersion: core.kubestash.com/v1alpha1 +kind: BackupConfiguration +metadata: + name: sample-qdrant-backup + namespace: demo +spec: + target: + apiGroup: kubedb.com + kind: Qdrant + namespace: demo + name: sample-qdrant + backends: + - name: minio-backend + storageRef: + namespace: demo + name: minio-storage + retentionPolicy: + name: demo-retention + namespace: demo + sessions: + - name: frequent-backup + scheduler: + schedule: "*/5 * * * *" + jobTemplate: + backoffLimit: 1 + repositories: + - name: minio-qdrant-repo + backend: minio-backend + directory: /qdrant + encryptionSecret: + name: encrypt-secret + namespace: demo + addon: + name: qdrant-addon + tasks: + - name: logical-backup + params: + collections: "my_collection" diff --git a/docs/guides/qdrant/backup/logical/examples/backupstorage.yaml b/docs/guides/qdrant/backup/logical/examples/backupstorage.yaml new file mode 100644 index 000000000..b6178875a --- /dev/null +++ b/docs/guides/qdrant/backup/logical/examples/backupstorage.yaml @@ -0,0 +1,20 @@ +apiVersion: storage.kubestash.com/v1alpha1 +kind: BackupStorage +metadata: + name: minio-storage + namespace: demo +spec: + storage: + provider: s3 + s3: + bucket: qdrant-backups + endpoint: http://minio.demo.svc:9000 + insecureTLS: true + prefix: backup/demo + region: us-east-1 + secretName: aws-secret + usagePolicy: + allowedNamespaces: + from: All + default: true + deletionPolicy: Delete diff --git a/docs/guides/qdrant/backup/logical/examples/minio.yaml b/docs/guides/qdrant/backup/logical/examples/minio.yaml new file mode 100644 index 000000000..c1035cbe5 --- /dev/null +++ b/docs/guides/qdrant/backup/logical/examples/minio.yaml @@ -0,0 +1,74 @@ +# MinIO Deployment +apiVersion: apps/v1 +kind: Deployment +metadata: + name: minio + namespace: demo +spec: + selector: + matchLabels: + app: minio + template: + metadata: + labels: + app: minio + spec: + containers: + - name: minio + image: minio/minio:latest + args: + - server + - /data + - --console-address + - ":9001" + env: + - name: MINIO_ROOT_USER + value: minioadmin + - name: MINIO_ROOT_PASSWORD + value: minioadmin + ports: + - containerPort: 9000 + - containerPort: 9001 + volumeMounts: + - name: data + mountPath: /data + volumes: + - name: data + emptyDir: {} +--- +# MinIO Service +apiVersion: v1 +kind: Service +metadata: + name: minio + namespace: demo +spec: + ports: + - name: api + port: 9000 + - name: console + port: 9001 + selector: + app: minio +--- +# MinIO Setup Job +apiVersion: batch/v1 +kind: Job +metadata: + name: minio-setup + namespace: demo +spec: + template: + spec: + containers: + - name: mc + image: minio/mc:latest + command: + - /bin/sh + - -c + - | + sleep 10 + mc alias set myminio http://minio:9000 minioadmin minioadmin + mc mb myminio/qdrant-backups --ignore-existing + echo "Bucket created" + restartPolicy: Never diff --git a/docs/guides/qdrant/backup/logical/examples/qdrant-restored.yaml b/docs/guides/qdrant/backup/logical/examples/qdrant-restored.yaml new file mode 100644 index 000000000..db0bbe966 --- /dev/null +++ b/docs/guides/qdrant/backup/logical/examples/qdrant-restored.yaml @@ -0,0 +1,19 @@ +apiVersion: kubedb.com/v1 +kind: Qdrant +metadata: + name: restored-qdrant + namespace: demo +spec: + init: + waitForInitialRestore: true + version: "1.17.0" + mode: Distributed + replicas: 3 + storage: + storageClassName: longhorn + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 200Mi + deletionPolicy: WipeOut diff --git a/docs/guides/qdrant/backup/logical/examples/qdrant.yaml b/docs/guides/qdrant/backup/logical/examples/qdrant.yaml new file mode 100644 index 000000000..76b85efcc --- /dev/null +++ b/docs/guides/qdrant/backup/logical/examples/qdrant.yaml @@ -0,0 +1,17 @@ +apiVersion: kubedb.com/v1 +kind: Qdrant +metadata: + name: sample-qdrant + namespace: demo +spec: + version: "1.17.0" + mode: Distributed + replicas: 3 + storage: + storageClassName: longhorn + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 200Mi + deletionPolicy: WipeOut diff --git a/docs/guides/qdrant/backup/logical/examples/restoresession.yaml b/docs/guides/qdrant/backup/logical/examples/restoresession.yaml new file mode 100644 index 000000000..ac813c298 --- /dev/null +++ b/docs/guides/qdrant/backup/logical/examples/restoresession.yaml @@ -0,0 +1,21 @@ +apiVersion: core.kubestash.com/v1alpha1 +kind: RestoreSession +metadata: + name: restore-sample-qdrant + namespace: demo +spec: + target: + apiGroup: kubedb.com + kind: Qdrant + namespace: demo + name: restored-qdrant + dataSource: + repository: minio-qdrant-repo + snapshot: latest + encryptionSecret: + name: encrypt-secret + namespace: demo + addon: + name: qdrant-addon + tasks: + - name: logical-backup-restore diff --git a/docs/guides/qdrant/backup/logical/examples/retentionpolicy.yaml b/docs/guides/qdrant/backup/logical/examples/retentionpolicy.yaml new file mode 100644 index 000000000..a3415eedc --- /dev/null +++ b/docs/guides/qdrant/backup/logical/examples/retentionpolicy.yaml @@ -0,0 +1,11 @@ +apiVersion: storage.kubestash.com/v1alpha1 +kind: RetentionPolicy +metadata: + name: demo-retention + namespace: demo +spec: + default: true + maxNumberOfSnapshots: 5 + usagePolicy: + allowedNamespaces: + from: All diff --git a/docs/guides/qdrant/backup/logical/examples/storage-secret.yaml b/docs/guides/qdrant/backup/logical/examples/storage-secret.yaml new file mode 100644 index 000000000..1a2312fed --- /dev/null +++ b/docs/guides/qdrant/backup/logical/examples/storage-secret.yaml @@ -0,0 +1,9 @@ +apiVersion: v1 +kind: Secret +metadata: + name: aws-secret + namespace: demo +type: Opaque +stringData: + AWS_ACCESS_KEY_ID: minioadmin + AWS_SECRET_ACCESS_KEY: minioadmin diff --git a/docs/guides/qdrant/backup/logical/index.md b/docs/guides/qdrant/backup/logical/index.md index 0085fc849..a8ccd27eb 100644 --- a/docs/guides/qdrant/backup/logical/index.md +++ b/docs/guides/qdrant/backup/logical/index.md @@ -23,16 +23,6 @@ KubeStash allows you to backup and restore `Qdrant` databases logically. This gu - Install KubeStash `kubectl` plugin following the steps [here](https://kubestash.com/docs/latest/setup/install/kubectl-plugin/). - If you are not familiar with how KubeStash backup and restore Qdrant databases, please check the following guide [here](/docs/guides/qdrant/backup/overview/index.md). -You should be familiar with the following `KubeStash` concepts: - -- [BackupStorage](https://kubestash.com/docs/latest/concepts/crds/backupstorage/) -- [BackupConfiguration](https://kubestash.com/docs/latest/concepts/crds/backupconfiguration/) -- [BackupSession](https://kubestash.com/docs/latest/concepts/crds/backupsession/) -- [RestoreSession](https://kubestash.com/docs/latest/concepts/crds/restoresession/) -- [Addon](https://kubestash.com/docs/latest/concepts/crds/addon/) -- [Function](https://kubestash.com/docs/latest/concepts/crds/function/) -- [Task](https://kubestash.com/docs/latest/concepts/crds/addon/#task-specification) - To keep everything isolated, we are going to use a separate namespace called `demo` throughout this tutorial. ```bash @@ -40,7 +30,7 @@ $ kubectl create ns demo namespace/demo created ``` -> **Note:** YAML files used in this tutorial are stored in [docs/guides/qdrant/backup/logical/examples](docs/guides/qdrant/backup/logical/examples) directory of [kubedb/docs](https://github.com/kubedb/docs) repository. +> **Note:** YAML files used in this tutorial are stored in [docs/guides/qdrant/backup/logical/examples](https://github.com/kubedb/docs/tree/{{< param "info.version" >}}/docs/guides/qdrant/backup/logical/examples) directory of [kubedb/docs](https://github.com/kubedb/docs) repository. ## Backup Qdrant @@ -79,7 +69,7 @@ spec: Create the above `Qdrant` CR, ```bash -$ kubectl apply -f https://github.com/kubedb/docs/raw/{{< param "info.version" >}}/docs/guides/qdrant/backup/logical/examples/sample-qdrant.yaml +$ kubectl apply -f https://github.com/kubedb/docs/raw/{{< param "info.version" >}}/docs/guides/qdrant/backup/logical/examples/qdrant.yaml qdrant.kubedb.com/sample-qdrant created ``` @@ -177,7 +167,7 @@ minio-xxxxxxxxxx-xxxxx 1/1 Running 0 2m Create a secret with credentials to access the MinIO storage: ```bash -$ kubectl apply -f https://github.com/kubedb/docs/raw/{{< param "info.version" >}}/docs/guides/qdrant/backup/logical/examples/aws-secret.yaml +$ kubectl apply -f https://github.com/kubedb/docs/raw/{{< param "info.version" >}}/docs/guides/qdrant/backup/logical/examples/storage-secret.yaml secret/aws-secret created ``` @@ -417,7 +407,7 @@ spec: Let's create the above database, ```bash -$ kubectl apply -f https://github.com/kubedb/docs/raw/{{< param "info.version" >}}/docs/guides/qdrant/backup/logical/examples/restored-qdrant.yaml +$ kubectl apply -f https://github.com/kubedb/docs/raw/{{< param "info.version" >}}/docs/guides/qdrant/backup/logical/examples/qdrant-restored.yaml qdrant.kubedb.com/restored-qdrant created ``` diff --git a/docs/guides/qdrant/backup/volumesnapshot/examples/backupconfiguration.yaml b/docs/guides/qdrant/backup/volumesnapshot/examples/backupconfiguration.yaml new file mode 100644 index 000000000..cfe5ed074 --- /dev/null +++ b/docs/guides/qdrant/backup/volumesnapshot/examples/backupconfiguration.yaml @@ -0,0 +1,38 @@ +apiVersion: core.kubestash.com/v1alpha1 +kind: BackupConfiguration +metadata: + name: sample-qdrant-backup + namespace: demo +spec: + target: + apiGroup: kubedb.com + kind: Qdrant + namespace: demo + name: sample-qdrant + backends: + - name: minio-backend + storageRef: + namespace: demo + name: minio-storage + retentionPolicy: + name: demo-retention + namespace: demo + sessions: + - name: frequent-backup + scheduler: + schedule: "*/5 * * * *" + jobTemplate: + backoffLimit: 1 + repositories: + - name: minio-qdrant-repo + backend: minio-backend + directory: /qdrant + encryptionSecret: + name: encrypt-secret + namespace: demo + addon: + name: qdrant-addon + tasks: + - name: volume-snapshot + params: + volumeSnapshotClassName: "longhorn-snapshot-vsc" diff --git a/docs/guides/qdrant/backup/volumesnapshot/examples/backupstorage.yaml b/docs/guides/qdrant/backup/volumesnapshot/examples/backupstorage.yaml new file mode 100644 index 000000000..b6178875a --- /dev/null +++ b/docs/guides/qdrant/backup/volumesnapshot/examples/backupstorage.yaml @@ -0,0 +1,20 @@ +apiVersion: storage.kubestash.com/v1alpha1 +kind: BackupStorage +metadata: + name: minio-storage + namespace: demo +spec: + storage: + provider: s3 + s3: + bucket: qdrant-backups + endpoint: http://minio.demo.svc:9000 + insecureTLS: true + prefix: backup/demo + region: us-east-1 + secretName: aws-secret + usagePolicy: + allowedNamespaces: + from: All + default: true + deletionPolicy: Delete diff --git a/docs/guides/qdrant/backup/volumesnapshot/examples/qdrant-restored.yaml b/docs/guides/qdrant/backup/volumesnapshot/examples/qdrant-restored.yaml new file mode 100644 index 000000000..db0bbe966 --- /dev/null +++ b/docs/guides/qdrant/backup/volumesnapshot/examples/qdrant-restored.yaml @@ -0,0 +1,19 @@ +apiVersion: kubedb.com/v1 +kind: Qdrant +metadata: + name: restored-qdrant + namespace: demo +spec: + init: + waitForInitialRestore: true + version: "1.17.0" + mode: Distributed + replicas: 3 + storage: + storageClassName: longhorn + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 200Mi + deletionPolicy: WipeOut diff --git a/docs/guides/qdrant/backup/volumesnapshot/examples/qdrant.yaml b/docs/guides/qdrant/backup/volumesnapshot/examples/qdrant.yaml new file mode 100644 index 000000000..76b85efcc --- /dev/null +++ b/docs/guides/qdrant/backup/volumesnapshot/examples/qdrant.yaml @@ -0,0 +1,17 @@ +apiVersion: kubedb.com/v1 +kind: Qdrant +metadata: + name: sample-qdrant + namespace: demo +spec: + version: "1.17.0" + mode: Distributed + replicas: 3 + storage: + storageClassName: longhorn + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 200Mi + deletionPolicy: WipeOut diff --git a/docs/guides/qdrant/backup/volumesnapshot/examples/restoresession.yaml b/docs/guides/qdrant/backup/volumesnapshot/examples/restoresession.yaml new file mode 100644 index 000000000..af0d4cd76 --- /dev/null +++ b/docs/guides/qdrant/backup/volumesnapshot/examples/restoresession.yaml @@ -0,0 +1,21 @@ +apiVersion: core.kubestash.com/v1alpha1 +kind: RestoreSession +metadata: + name: restore-sample-qdrant + namespace: demo +spec: + target: + apiGroup: kubedb.com + kind: Qdrant + namespace: demo + name: restored-qdrant + dataSource: + repository: minio-qdrant-repo + snapshot: latest + encryptionSecret: + name: encrypt-secret + namespace: demo + addon: + name: qdrant-addon + tasks: + - name: volume-snapshot-restore diff --git a/docs/guides/qdrant/backup/volumesnapshot/examples/retentionpolicy.yaml b/docs/guides/qdrant/backup/volumesnapshot/examples/retentionpolicy.yaml new file mode 100644 index 000000000..a3415eedc --- /dev/null +++ b/docs/guides/qdrant/backup/volumesnapshot/examples/retentionpolicy.yaml @@ -0,0 +1,11 @@ +apiVersion: storage.kubestash.com/v1alpha1 +kind: RetentionPolicy +metadata: + name: demo-retention + namespace: demo +spec: + default: true + maxNumberOfSnapshots: 5 + usagePolicy: + allowedNamespaces: + from: All diff --git a/docs/guides/qdrant/backup/volumesnapshot/examples/storage-secret.yaml b/docs/guides/qdrant/backup/volumesnapshot/examples/storage-secret.yaml new file mode 100644 index 000000000..1a2312fed --- /dev/null +++ b/docs/guides/qdrant/backup/volumesnapshot/examples/storage-secret.yaml @@ -0,0 +1,9 @@ +apiVersion: v1 +kind: Secret +metadata: + name: aws-secret + namespace: demo +type: Opaque +stringData: + AWS_ACCESS_KEY_ID: minioadmin + AWS_SECRET_ACCESS_KEY: minioadmin diff --git a/docs/guides/qdrant/backup/volumesnapshot/examples/volumesnapshotclass.yaml b/docs/guides/qdrant/backup/volumesnapshot/examples/volumesnapshotclass.yaml new file mode 100644 index 000000000..e250158bc --- /dev/null +++ b/docs/guides/qdrant/backup/volumesnapshot/examples/volumesnapshotclass.yaml @@ -0,0 +1,8 @@ +apiVersion: snapshot.storage.k8s.io/v1 +kind: VolumeSnapshotClass +metadata: + name: longhorn-snapshot-vsc +driver: driver.longhorn.io +deletionPolicy: Delete +parameters: + type: snap diff --git a/docs/guides/qdrant/backup/volumesnapshot/index.md b/docs/guides/qdrant/backup/volumesnapshot/index.md index 4079bd864..7ddda5b7d 100644 --- a/docs/guides/qdrant/backup/volumesnapshot/index.md +++ b/docs/guides/qdrant/backup/volumesnapshot/index.md @@ -40,7 +40,7 @@ $ kubectl create ns demo namespace/demo created ``` -> **Note:** YAML files used in this tutorial are stored in [docs/guides/qdrant/backup/volumesnapshot/examples](docs/guides/qdrant/backup/volumesnapshot/examples) directory of [kubedb/docs](https://github.com/kubedb/docs) repository. +> **Note:** YAML files used in this tutorial are stored in [docs/guides/qdrant/backup/volumesnapshot/examples](https://github.com/kubedb/docs/tree/{{< param "info.version" >}}/docs/guides/qdrant/backup/volumesnapshot/examples) directory of [kubedb/docs](https://github.com/kubedb/docs) repository. ## Prepare Backup Infrastructure @@ -115,7 +115,7 @@ backupstorage.storage.kubestash.com/minio-storage created Create a secret with credentials to access the storage: ```bash -$ kubectl apply -f https://github.com/kubedb/docs/raw/{{< param "info.version" >}}/docs/guides/qdrant/backup/volumesnapshot/examples/aws-secret.yaml +$ kubectl apply -f https://github.com/kubedb/docs/raw/{{< param "info.version" >}}/docs/guides/qdrant/backup/volumesnapshot/examples/storage-secret.yaml secret/aws-secret created ``` @@ -188,7 +188,7 @@ spec: Create the above `Qdrant` CR, ```bash -$ kubectl apply -f https://github.com/kubedb/docs/raw/{{< param "info.version" >}}/docs/guides/qdrant/backup/volumesnapshot/examples/sample-qdrant.yaml +$ kubectl apply -f https://github.com/kubedb/docs/raw/{{< param "info.version" >}}/docs/guides/qdrant/backup/volumesnapshot/examples/qdrant.yaml qdrant.kubedb.com/sample-qdrant created ``` @@ -369,7 +369,7 @@ spec: Let's create the above database, ```bash -$ kubectl apply -f https://github.com/kubedb/docs/raw/{{< param "info.version" >}}/docs/guides/qdrant/backup/volumesnapshot/examples/restored-qdrant.yaml +$ kubectl apply -f https://github.com/kubedb/docs/raw/{{< param "info.version" >}}/docs/guides/qdrant/backup/volumesnapshot/examples/qdrant-restored.yaml qdrant.kubedb.com/restored-qdrant created ``` diff --git a/docs/guides/qdrant/concepts/_index.md b/docs/guides/qdrant/concepts/_index.md index 4f82fb0c8..761f029b9 100644 --- a/docs/guides/qdrant/concepts/_index.md +++ b/docs/guides/qdrant/concepts/_index.md @@ -7,4 +7,157 @@ menu: parent: qdrant-guides weight: 20 menu_name: docs_{{ .version }} ---- \ No newline at end of file +--- + +> New to KubeDB? Please start [here](/docs/README.md). + +# Qdrant + +## What is Qdrant + +`Qdrant` is a Kubernetes `Custom Resource Definitions` (CRD). It provides declarative configuration for [Qdrant](https://qdrant.tech/) vector databases in a Kubernetes native way. You only need to describe the desired database configuration in a `Qdrant` object, and the KubeDB operator will create Kubernetes objects in the desired state for you. + +## Qdrant Spec + +As with all other Kubernetes objects, a `Qdrant` CR needs `apiVersion`, `kind`, and `metadata` fields. It also needs a `.spec` section. + +Below is an example `Qdrant` object: + +```yaml +apiVersion: kubedb.com/v1alpha2 +kind: Qdrant +metadata: + name: qdrant-sample + namespace: demo +spec: + version: "1.17.0" + replicas: 3 + authSecret: + name: qdrant-sample-auth + configSecret: + name: qdrant-config + storageType: Durable + storage: + storageClassName: standard + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 1Gi + tls: + issuerRef: + apiGroup: "cert-manager.io" + kind: Issuer + name: qdrant-issuer + monitor: + agent: prometheus.io/operator + prometheus: + serviceMonitor: + labels: + app: kubedb + interval: 10s + podTemplate: + metadata: + annotations: + passMe: ToDatabasePod + spec: + serviceAccountName: my-custom-sa + nodeSelector: + disktype: ssd + imagePullSecrets: + - name: myregistrykey + containers: + - name: qdrant + resources: + requests: + memory: "512Mi" + cpu: "500m" + limits: + memory: "1Gi" + cpu: "1" + serviceTemplates: + - alias: primary + spec: + type: LoadBalancer + deletionPolicy: Halt +``` + +### spec.version + +`spec.version` is a required field that specifies the name of the [QdrantVersion](/docs/guides/qdrant/concepts/catalog.md) CRD where the docker images are specified. Currently, when you install KubeDB, it creates the following `QdrantVersion` CRDs: + +```bash +$ kubectl get qdrantversions +NAME VERSION DB_IMAGE DEPRECATED AGE +1.7.4 1.7.4 qdrant/qdrant:v1.7.4 3d +1.10.0 1.10.0 qdrant/qdrant:v1.10.0 3d +1.14.0 1.14.0 qdrant/qdrant:v1.14.0 3d +1.17.0 1.17.0 qdrant/qdrant:v1.17.0 3d +``` + +### spec.replicas + +`spec.replicas` is an optional field that specifies the number of Qdrant pods to run. For a single-node deployment, set it to `1`. For a multi-node cluster, set it to the desired number of replicas (e.g., `3`). + +### spec.authSecret + +`spec.authSecret` is an optional field that points to a Secret used for Qdrant API key authentication. If not provided, KubeDB will create one automatically. The Secret must contain an `api-key` data field. + +### spec.configSecret + +`spec.configSecret` is an optional field that points to a Secret containing a custom `production.yaml` configuration file for Qdrant. See [Custom Configuration](/docs/guides/qdrant/configuration/using-config-file.md) for details. + +### spec.storageType + +`spec.storageType` specifies the type of storage that will be used for Qdrant. It can be `Durable` or `Ephemeral`. The default value is `Durable`. If `Ephemeral` is used, KubeDB will create Qdrant using `EmptyDir` volume. In this case, you don't have to specify `spec.storage`. This is useful for testing purposes. + +### spec.storage + +`spec.storage` specifies the StorageClass of PVCs that will be dynamically allocated to store data for Qdrant pods. If `spec.storageType: Ephemeral` is not set, this field is required. + +### spec.tls + +`spec.tls` specifies TLS/SSL configurations for Qdrant. It contains the following sub-fields: + +- `spec.tls.issuerRef` points to a cert-manager `Issuer` or `ClusterIssuer` used to issue certificates. +- `spec.tls.certificates` is an optional field that lists additional certificates for the Qdrant server. + +### spec.monitor + +`spec.monitor` specifies the monitoring configuration for Qdrant. It contains the following sub-field: + +- `spec.monitor.agent` specifies the monitoring agent. Valid values are `prometheus.io/builtin` and `prometheus.io/operator`. + +### spec.podTemplate + +KubeDB allows providing a template for database pods through `spec.podTemplate`. KubeDB operator will pass the information provided in `spec.podTemplate` to the StatefulSet created for Qdrant database. Notable sub-fields include: + +- `spec.podTemplate.spec.serviceAccountName` to provide a custom ServiceAccount. +- `spec.podTemplate.spec.imagePullSecrets` to pull images from a private registry. +- `spec.podTemplate.spec.nodeSelector` to schedule pods on specific nodes. +- `spec.podTemplate.spec.containers[].resources` to configure CPU and memory resources. + +### spec.serviceTemplates + +`spec.serviceTemplates` is an optional field that contains a list of the service templates for the Qdrant services. KubeDB allows following service template variables: + +- `spec.serviceTemplates[].alias`: specifies which service template (e.g., `primary`). +- `spec.serviceTemplates[].spec.type`: specifies the service type (e.g., `ClusterIP`, `LoadBalancer`, `NodePort`). + +### spec.deletionPolicy + +`spec.deletionPolicy` gives freedom to the user to control the behavior of KubeDB when a Qdrant object is deleted. Possible values are: + +- `DoNotTerminate`: prevents deletion of the object if admission webhook is enabled. +- `Halt`: deletes the Qdrant object but keeps the underlying resources (PVCs, Secrets) intact. +- `Delete`: deletes the Qdrant object and its PVCs, but not Secrets. +- `WipeOut`: deletes the Qdrant object and all related resources including PVCs and Secrets. + +### spec.disableSecurity + +`spec.disableSecurity` is an optional boolean field that disables API key authentication when set to `true`. The default is `false`. + +## Next Steps + +- Learn about [QdrantVersion CRD](/docs/guides/qdrant/concepts/catalog.md). +- Deploy your first Qdrant database with KubeDB by following the guide [here](/docs/guides/qdrant/quickstart/quickstart.md). \ No newline at end of file diff --git a/docs/guides/qdrant/concepts/autoscaler.md b/docs/guides/qdrant/concepts/autoscaler.md index 9b8d495b3..ebb6b0fc7 100644 --- a/docs/guides/qdrant/concepts/autoscaler.md +++ b/docs/guides/qdrant/concepts/autoscaler.md @@ -76,7 +76,7 @@ A `QdrantAutoscaler` object has the following fields in the `spec` section: #### spec.databaseRef -`spec.databaseRef` is a required field that points to the [Qdrant](/docs/guides/qdrant/concepts/qdrant.md) object for which autoscaling will be performed. It contains: +`spec.databaseRef` is a required field that points to the [Qdrant](/docs/guides/qdrant/concepts/) object for which autoscaling will be performed. It contains: - `spec.databaseRef.name` — the name of the target Qdrant database (required). @@ -111,4 +111,4 @@ A `QdrantAutoscaler` object has the following fields in the `spec` section: ## Next Steps - Read the [Qdrant autoscaler overview](/docs/guides/qdrant/autoscaler/overview.md). -- See the [compute autoscaler guide](/docs/guides/qdrant/autoscaler/compute/cluster.md) and [storage autoscaler guide](/docs/guides/qdrant/autoscaler/storage/cluster.md). \ No newline at end of file +- See the [compute autoscaler guide](/docs/guides/qdrant/autoscaler/compute/database.md) and [storage autoscaler guide](/docs/guides/qdrant/autoscaler/storage/database.md). \ No newline at end of file diff --git a/docs/guides/qdrant/concepts/qdrant.md b/docs/guides/qdrant/concepts/qdrant.md deleted file mode 100644 index 7767290f2..000000000 --- a/docs/guides/qdrant/concepts/qdrant.md +++ /dev/null @@ -1,164 +0,0 @@ ---- -title: Qdrant CRD -menu: - docs_{{ .version }}: - identifier: qdrant-concepts-qdrant - name: Qdrant - parent: qdrant-concepts - weight: 10 -menu_name: docs_{{ .version }} -section_menu_id: guides ---- - -> New to KubeDB? Please start [here](/docs/README.md). - -# Qdrant - -## What is Qdrant - -`Qdrant` is a Kubernetes `Custom Resource Definitions` (CRD). It provides declarative configuration for [Qdrant](https://qdrant.tech/) vector databases in a Kubernetes native way. You only need to describe the desired database configuration in a `Qdrant` object, and the KubeDB operator will create Kubernetes objects in the desired state for you. - -## Qdrant Spec - -As with all other Kubernetes objects, a `Qdrant` CR needs `apiVersion`, `kind`, and `metadata` fields. It also needs a `.spec` section. - -Below is an example `Qdrant` object: - -```yaml -apiVersion: kubedb.com/v1alpha2 -kind: Qdrant -metadata: - name: qdrant-sample - namespace: demo -spec: - version: "1.17.0" - replicas: 3 - authSecret: - name: qdrant-sample-auth - configSecret: - name: qdrant-config - storageType: Durable - storage: - storageClassName: standard - accessModes: - - ReadWriteOnce - resources: - requests: - storage: 1Gi - tls: - issuerRef: - apiGroup: "cert-manager.io" - kind: Issuer - name: qdrant-issuer - monitor: - agent: prometheus.io/operator - prometheus: - serviceMonitor: - labels: - app: kubedb - interval: 10s - podTemplate: - metadata: - annotations: - passMe: ToDatabasePod - spec: - serviceAccountName: my-custom-sa - nodeSelector: - disktype: ssd - imagePullSecrets: - - name: myregistrykey - containers: - - name: qdrant - resources: - requests: - memory: "512Mi" - cpu: "500m" - limits: - memory: "1Gi" - cpu: "1" - serviceTemplates: - - alias: primary - spec: - type: LoadBalancer - deletionPolicy: Halt -``` - -### spec.version - -`spec.version` is a required field that specifies the name of the [QdrantVersion](/docs/guides/qdrant/concepts/catalog.md) CRD where the docker images are specified. Currently, when you install KubeDB, it creates the following `QdrantVersion` CRDs: - -```bash -$ kubectl get qdrantversions -NAME VERSION DB_IMAGE DEPRECATED AGE -1.7.4 1.7.4 qdrant/qdrant:v1.7.4 3d -1.10.0 1.10.0 qdrant/qdrant:v1.10.0 3d -1.14.0 1.14.0 qdrant/qdrant:v1.14.0 3d -1.17.0 1.17.0 qdrant/qdrant:v1.17.0 3d -``` - -### spec.replicas - -`spec.replicas` is an optional field that specifies the number of Qdrant pods to run. For a single-node deployment, set it to `1`. For a multi-node cluster, set it to the desired number of replicas (e.g., `3`). - -### spec.authSecret - -`spec.authSecret` is an optional field that points to a Secret used for Qdrant API key authentication. If not provided, KubeDB will create one automatically. The Secret must contain an `api-key` data field. - -### spec.configSecret - -`spec.configSecret` is an optional field that points to a Secret containing a custom `production.yaml` configuration file for Qdrant. See [Custom Configuration](/docs/guides/qdrant/configuration/using-config-file.md) for details. - -### spec.storageType - -`spec.storageType` specifies the type of storage that will be used for Qdrant. It can be `Durable` or `Ephemeral`. The default value is `Durable`. If `Ephemeral` is used, KubeDB will create Qdrant using `EmptyDir` volume. In this case, you don't have to specify `spec.storage`. This is useful for testing purposes. - -### spec.storage - -`spec.storage` specifies the StorageClass of PVCs that will be dynamically allocated to store data for Qdrant pods. If `spec.storageType: Ephemeral` is not set, this field is required. - -### spec.tls - -`spec.tls` specifies TLS/SSL configurations for Qdrant. It contains the following sub-fields: - -- `spec.tls.issuerRef` points to a cert-manager `Issuer` or `ClusterIssuer` used to issue certificates. -- `spec.tls.certificates` is an optional field that lists additional certificates for the Qdrant server. - -### spec.monitor - -`spec.monitor` specifies the monitoring configuration for Qdrant. It contains the following sub-field: - -- `spec.monitor.agent` specifies the monitoring agent. Valid values are `prometheus.io/builtin` and `prometheus.io/operator`. - -### spec.podTemplate - -KubeDB allows providing a template for database pods through `spec.podTemplate`. KubeDB operator will pass the information provided in `spec.podTemplate` to the StatefulSet created for Qdrant database. Notable sub-fields include: - -- `spec.podTemplate.spec.serviceAccountName` to provide a custom ServiceAccount. -- `spec.podTemplate.spec.imagePullSecrets` to pull images from a private registry. -- `spec.podTemplate.spec.nodeSelector` to schedule pods on specific nodes. -- `spec.podTemplate.spec.containers[].resources` to configure CPU and memory resources. - -### spec.serviceTemplates - -`spec.serviceTemplates` is an optional field that contains a list of the service templates for the Qdrant services. KubeDB allows following service template variables: - -- `spec.serviceTemplates[].alias`: specifies which service template (e.g., `primary`). -- `spec.serviceTemplates[].spec.type`: specifies the service type (e.g., `ClusterIP`, `LoadBalancer`, `NodePort`). - -### spec.deletionPolicy - -`spec.deletionPolicy` gives freedom to the user to control the behavior of KubeDB when a Qdrant object is deleted. Possible values are: - -- `DoNotTerminate`: prevents deletion of the object if admission webhook is enabled. -- `Halt`: deletes the Qdrant object but keeps the underlying resources (PVCs, Secrets) intact. -- `Delete`: deletes the Qdrant object and its PVCs, but not Secrets. -- `WipeOut`: deletes the Qdrant object and all related resources including PVCs and Secrets. - -### spec.disableSecurity - -`spec.disableSecurity` is an optional boolean field that disables API key authentication when set to `true`. The default is `false`. - -## Next Steps - -- Learn about [QdrantVersion CRD](/docs/guides/qdrant/concepts/catalog.md). -- Deploy your first Qdrant database with KubeDB by following the guide [here](/docs/guides/qdrant/quickstart/quickstart.md). \ No newline at end of file From 548f529eb2b784c6784d1a8eb7a11657a005fa89 Mon Sep 17 00:00:00 2001 From: Abdullah Al Masood <98589987+al-masood@users.noreply.github.com> Date: Wed, 13 May 2026 15:44:49 +0600 Subject: [PATCH 06/10] added missing things compared to mssql Signed-off-by: Abdullah Al Masood <98589987+al-masood@users.noreply.github.com> --- docs/guides/qdrant/README.md | 8 +- .../qdrant/autoscaler/compute/database.md | 2 + .../qdrant/autoscaler/compute/overview.md | 2 + .../qdrant/autoscaler/storage/overview.md | 2 + docs/guides/qdrant/backup/_index.md | 1 + docs/guides/qdrant/backup/logical/index.md | 15 ++ docs/guides/qdrant/backup/overview/index.md | 79 ++++++ .../qdrant/backup/volumesnapshot/index.md | 6 + docs/guides/qdrant/concepts/opsrequest.md | 50 ++++ docs/guides/qdrant/quickstart/quickstart.md | 248 +++++++++++++++++- .../qdrant/reconfigure-tls/reconfigure-tls.md | 10 + docs/guides/qdrant/reconfigure/reconfigure.md | 12 +- docs/guides/qdrant/restart/restart.md | 6 + docs/guides/qdrant/rotate-auth/rotateauth.md | 15 ++ .../scale-horizontally/index.md | 10 + .../scale-vertically/index.md | 10 + docs/guides/qdrant/tls/configure/index.md | 11 +- .../update-version/versionupgrading/index.md | 11 + .../volume-expansion/volume-expansion.md | 6 + 19 files changed, 493 insertions(+), 11 deletions(-) create mode 100644 docs/guides/qdrant/backup/overview/index.md diff --git a/docs/guides/qdrant/README.md b/docs/guides/qdrant/README.md index 1c0cb4d6d..494f72f24 100644 --- a/docs/guides/qdrant/README.md +++ b/docs/guides/qdrant/README.md @@ -17,7 +17,7 @@ aliases: # Overview -KubeDB supports Qdrant vector databases through the `Qdrant` CRD. +Qdrant is a high-performance open-source vector database designed for similarity search and AI-powered applications. KubeDB supports provisioning and management of Qdrant clusters directly inside Kubernetes, enabling scalable and production-ready vector search infrastructure with minimal operational overhead. Deploy Qdrant in distributed mode to achieve horizontal scalability, replication, and high availability for large-scale embedding workloads. KubeDB automates cluster lifecycle management tasks such as deployment, scaling, monitoring, backups, and version upgrades, simplifying operations for machine learning and semantic search applications. With seamless Kubernetes integration, users can efficiently run and manage resilient Qdrant deployments for modern AI and retrieval-augmented generation (RAG) workloads. ## Supported Qdrant Features @@ -32,6 +32,12 @@ KubeDB supports Qdrant vector databases through the `Qdrant` CRD. | Ops Requests | ✓ | | Autoscaler | ✓ | +## Supported Microsoft SQL Server Versions + +KubeDB supports the following Microsoft SQL Server Version. +- `1.15.4` +- `1.16.2` +- `1.17.0` ## Life Cycle of a Qdrant Object diff --git a/docs/guides/qdrant/autoscaler/compute/database.md b/docs/guides/qdrant/autoscaler/compute/database.md index 589935c5d..2de4c9b43 100644 --- a/docs/guides/qdrant/autoscaler/compute/database.md +++ b/docs/guides/qdrant/autoscaler/compute/database.md @@ -153,6 +153,8 @@ Here, - `spec.compute.node.maxAllowed` specifies the maximum allowed resources for the Qdrant nodes. - `spec.compute.node.controlledResources` specifies the resource types that will be auto-scaled. - `spec.compute.node.containerControlledValues` specifies which resource values should be controlled, here both requests and limits. +- `spec.opsRequestOptions.apply` has two supported values: `IfReady` and `Always`. Use `IfReady` to process the ops request only when the database is Ready. Use `Always` to process the execution irrespective of the database state. +- `spec.opsRequestOptions.timeout` specifies the maximum time for each step of the ops request. If a step doesn't finish within the specified timeout, the ops request will result in failure. Let's create the `QdrantAutoscaler` CR we have shown above: diff --git a/docs/guides/qdrant/autoscaler/compute/overview.md b/docs/guides/qdrant/autoscaler/compute/overview.md index 16ce05eef..1a74801e7 100644 --- a/docs/guides/qdrant/autoscaler/compute/overview.md +++ b/docs/guides/qdrant/autoscaler/compute/overview.md @@ -10,6 +10,8 @@ menu_name: docs_{{ .version }} section_menu_id: guides --- +> New to KubeDB? Please start [here](/docs/README.md). + # Qdrant Compute Autoscaler This guide shows how to configure compute autoscaling for Qdrant. diff --git a/docs/guides/qdrant/autoscaler/storage/overview.md b/docs/guides/qdrant/autoscaler/storage/overview.md index 7d0dd5155..a13a37c72 100644 --- a/docs/guides/qdrant/autoscaler/storage/overview.md +++ b/docs/guides/qdrant/autoscaler/storage/overview.md @@ -10,6 +10,8 @@ menu_name: docs_{{ .version }} section_menu_id: guides --- +> New to KubeDB? Please start [here](/docs/README.md). + # Qdrant Storage Autoscaler This guide shows how to configure storage autoscaling for Qdrant. diff --git a/docs/guides/qdrant/backup/_index.md b/docs/guides/qdrant/backup/_index.md index 2bc61031c..6efdd7d9b 100644 --- a/docs/guides/qdrant/backup/_index.md +++ b/docs/guides/qdrant/backup/_index.md @@ -7,4 +7,5 @@ menu: parent: qdrant-guides weight: 60 menu_name: docs_{{ .version }} +section_menu_id: guides --- diff --git a/docs/guides/qdrant/backup/logical/index.md b/docs/guides/qdrant/backup/logical/index.md index a8ccd27eb..dcc5a08bd 100644 --- a/docs/guides/qdrant/backup/logical/index.md +++ b/docs/guides/qdrant/backup/logical/index.md @@ -23,6 +23,16 @@ KubeStash allows you to backup and restore `Qdrant` databases logically. This gu - Install KubeStash `kubectl` plugin following the steps [here](https://kubestash.com/docs/latest/setup/install/kubectl-plugin/). - If you are not familiar with how KubeStash backup and restore Qdrant databases, please check the following guide [here](/docs/guides/qdrant/backup/overview/index.md). +You should be familiar with the following `KubeStash` concepts: + +- [BackupStorage](https://kubestash.com/docs/latest/concepts/crds/backupstorage/) +- [BackupConfiguration](https://kubestash.com/docs/latest/concepts/crds/backupconfiguration/) +- [BackupSession](https://kubestash.com/docs/latest/concepts/crds/backupsession/) +- [RestoreSession](https://kubestash.com/docs/latest/concepts/crds/restoresession/) +- [Addon](https://kubestash.com/docs/latest/concepts/crds/addon/) +- [Function](https://kubestash.com/docs/latest/concepts/crds/function/) +- [Task](https://kubestash.com/docs/latest/concepts/crds/addon/#task-specification) + To keep everything isolated, we are going to use a separate namespace called `demo` throughout this tutorial. ```bash @@ -292,6 +302,11 @@ spec: collections: "my_collection" ``` +Here, +- `.spec.sessions[*].schedule` specifies that we want to backup the database at `5 minutes` interval. +- `.spec.target` refers to the targeted `sample-qdrant` Qdrant database that we created earlier. +- `.spec.sessions[*].addon.tasks[*].name` specifies that the `logical-backup` task will be executed. The `params.collections` field can be used to specify which collection(s) to backup. + Let's create the `BackupConfiguration` CR that we have shown above, ```bash diff --git a/docs/guides/qdrant/backup/overview/index.md b/docs/guides/qdrant/backup/overview/index.md new file mode 100644 index 000000000..a54a7f57e --- /dev/null +++ b/docs/guides/qdrant/backup/overview/index.md @@ -0,0 +1,79 @@ +--- +title: Backup & Restore Qdrant Using KubeStash +menu: + docs_{{ .version }}: + identifier: guides-qdrant-backup-overview + name: Overview + parent: qdrant-backup + weight: 5 +menu_name: docs_{{ .version }} +section_menu_id: guides +--- + +> New to KubeDB? Please start [here](/docs/README.md). + +{{< notice type="warning" message="Please install [KubeStash](https://kubestash.com/docs/latest/setup/install/kubestash/) to try this feature. Database backup with KubeStash is already included in the KubeDB license. So, you don't need a separate license for KubeStash." >}} + +# Qdrant Backup & Restore Overview + +KubeDB uses [KubeStash](https://kubestash.com) to backup and restore databases. KubeStash by AppsCode is a cloud native data backup and recovery solution for Kubernetes workloads and databases. KubeStash utilizes [restic](https://github.com/restic/restic) to securely backup stateful applications to any cloud or on-prem storage backends (for example, S3, GCS, Azure Blob storage, Minio, NetApp, Dell EMC etc.). + +## How Backup Works + +The backup process consists of the following steps: + +1. At first, a user creates a `Secret`. This secret holds the credentials to access the backend where the backed up data will be stored. + +2. Then, she creates a `BackupStorage` custom resource that specifies the backend information, along with the `Secret` containing the credentials needed to access the backend. + +3. KubeStash operator watches for `BackupStorage` custom resources. When it finds a `BackupStorage` object, it initializes the `BackupStorage` by uploading the `metadata.yaml` file to the specified backend. + +4. Next, she creates a `BackupConfiguration` custom resource that specifies the target database, addon information (including backup tasks), backup schedules, storage backends for storing the backup data, and other additional settings. + +5. KubeStash operator watches for `BackupConfiguration` objects. + +6. Once the KubeStash operator finds a `BackupConfiguration` object, it creates `Repository` with the information specified in the `BackupConfiguration`. + +7. KubeStash operator watches for `Repository` custom resources. When it finds the `Repository` object, it Initializes `Repository` by uploading `repository.yaml` file into the `spec.sessions[*].repositories[*].directory` path specified in `BackupConfiguration`. + +8. Then, it creates a `CronJob` for each session with the schedule specified in `BackupConfiguration` to trigger backup periodically. + +9. KubeStash operator triggers an instant backup as soon as the `BackupConfiguration` is ready. Backups are otherwise triggered by the `CronJob` based on the specified schedule. + +10. KubeStash operator watches for `BackupSession` custom resources. + +11. When it finds a `BackupSession` object, it creates a `Snapshot` custom resource for each `Repository` specified in the `BackupConfiguration`. + +12. Then it resolves the respective `Addon` and `Function` and prepares backup `Job` definition. + +13. Then, it creates the `Job` to backup the targeted `Qdrant` database. + +14. The backup `Job` reads necessary information (e.g. auth secret, port) to connect with the database from the `AppBinding` CR. It also reads backend information and access credentials from `BackupStorage` CR, Storage Secret and `Repository` path respectively. + +15. Then, the `Job` dumps the targeted `Qdrant` database and uploads the output to the backend. KubeStash pipes the output of dump command to uploading process. Hence, backup `Job` does not require a large volume to hold the entire dump output. + +16. After the backup process is completed, the backup `Job` updates the `status.components[dump]` field of the `Snapshot` resources with backup information of the target `Qdrant` database. + +## How Restore Process Works + +The restore process consists of the following steps: + +1. At first, a user creates a `Qdrant` database where the data will be restored or the user can use the same `Qdrant` database. + +2. Then, she creates a `RestoreSession` custom resource that specifies the target database where the backed-up data will be restored, addon information (including restore tasks), the target snapshot to be restored, the Repository containing that snapshot, and other additional settings. + +3. KubeStash operator watches for `RestoreSession` custom resources. + +4. When it finds a `RestoreSession` custom resource, it resolves the respective `Addon` and `Function` and prepares a restore `Job` definition. + +5. Then, it creates the `Job` to restore the target. + +6. The `Job` reads necessary information to connect with the database from respective `AppBinding` CR. It also reads backend information and access credentials from `Repository` CR and storage `Secret` respectively. + +7. Then, the `Job` downloads the backed up data from the backend and injects into the desired database. KubeStash pipes the downloaded data to the respective database tool to inject into the database. Hence, restore `Job` does not require a large volume to download entire backup data inside it. + +8. Finally, when the restore process is completed, the `Job` updates the `status.components[*]` field of the `RestoreSession` with restore information of the target database. + +## Next Steps + +- Backup a `Qdrant` database using KubeStash by the following guides from [here](/docs/guides/qdrant/backup/logical/index.md). diff --git a/docs/guides/qdrant/backup/volumesnapshot/index.md b/docs/guides/qdrant/backup/volumesnapshot/index.md index 7ddda5b7d..fee50588f 100644 --- a/docs/guides/qdrant/backup/volumesnapshot/index.md +++ b/docs/guides/qdrant/backup/volumesnapshot/index.md @@ -32,6 +32,7 @@ You should be familiar with the following `KubeStash` concepts: - [RestoreSession](https://kubestash.com/docs/latest/concepts/crds/restoresession/) - [Addon](https://kubestash.com/docs/latest/concepts/crds/addon/) - [Function](https://kubestash.com/docs/latest/concepts/crds/function/) +- [Task](https://kubestash.com/docs/latest/concepts/crds/addon/#task-specification) To keep things isolated, we are going to use a separate namespace called `demo` throughout this tutorial. @@ -282,6 +283,11 @@ spec: volumeSnapshotClassName: "longhorn-snapshot-vsc" ``` +Here, +- `.spec.sessions[*].schedule` specifies that we want to backup the database at `5 minutes` interval. +- `.spec.target` refers to the targeted `sample-qdrant` Qdrant database that we created earlier. +- `.spec.sessions[*].addon.tasks[*].params[*].volumeSnapshotClassName` specifies the `VolumeSnapshotClass` to use for creating volume snapshots. + Let's create the `BackupConfiguration` CR that we have shown above, ```bash diff --git a/docs/guides/qdrant/concepts/opsrequest.md b/docs/guides/qdrant/concepts/opsrequest.md index 4430ac872..18bef6587 100644 --- a/docs/guides/qdrant/concepts/opsrequest.md +++ b/docs/guides/qdrant/concepts/opsrequest.md @@ -159,6 +159,56 @@ A `QdrantOpsRequest` object has the following fields in the `spec` section: `spec.apply` is an optional field that specifies when the OpsRequest will be applied. Possible values are `Always` and `IfReady`. The default is `IfReady`, which means the OpsRequest will only be applied when the target database is in `Ready` state. +### QdrantOpsRequest `Status` + +`.status` describes the current state and progress of the `QdrantOpsRequest` operation. It has the following fields: + +#### status.phase + +`status.phase` indicates the overall phase of the operation for this `QdrantOpsRequest`. It can have the following values: + +| Phase | Meaning | +|-------------|---------------------------------------------------------------------------------| +| Successful | KubeDB has successfully performed the operation requested in the QdrantOpsRequest | +| Progressing | KubeDB has started the execution of the applied QdrantOpsRequest | +| Failed | KubeDB has failed the operation requested in the QdrantOpsRequest | +| Denied | KubeDB has denied the operation requested in the QdrantOpsRequest | +| Skipped | KubeDB has skipped the operation requested in the QdrantOpsRequest | + +Ops-manager Operator can skip an opsRequest only if its execution has not been started yet and there is a newer opsRequest applied in the cluster. `spec.type` has to be the same as the skipped one, in this case. + +#### status.observedGeneration + +`status.observedGeneration` shows the most recent generation observed by the `QdrantOpsRequest` controller. + +#### status.conditions + +`status.conditions` is an array that specifies the conditions of different steps of `QdrantOpsRequest` processing. Each condition entry has the following fields: + +- `type` specifies the type of the condition. QdrantOpsRequest has the following types of conditions: + +| Type | Meaning | +|---------------------|----------------------------------------------------------------------------------| +| `Progressing` | Specifies that the operation is now progressing | +| `Successful` | Specifies that the operation on the database has been successful | +| `HaltDatabase` | Specifies that the database is halted by the operator | +| `ResumeDatabase` | Specifies that the database is resumed by the operator | +| `Failed` | Specifies that the operation on the database has failed | +| `Scaling` | Specifies that the scaling operation on the database has started | +| `VerticalScaling` | Specifies that vertical scaling has performed successfully on the database | +| `HorizontalScaling` | Specifies that horizontal scaling has performed successfully on the database | +| `Updating` | Specifies that the database updating operation has started | +| `UpdateVersion` | Specifies that version updating on the database has performed successfully | + +- The `status` field is a string, with possible values `"True"`, `"False"`, and `"Unknown"`. + - `status` will be `"True"` if the current transition is succeeded. + - `status` will be `"False"` if the current transition is failed. + - `status` will be `"Unknown"` if the current transition is denied. +- The `message` field is a human-readable message indicating details about the condition. +- The `reason` field is a unique, one-word, CamelCase reason for the condition's last transition. +- The `lastTransitionTime` field provides a timestamp for when the operation last transitioned from one state to another. +- The `observedGeneration` shows the most recent condition transition generation observed by the controller. + ## Next Steps - See [Qdrant ops request overview](/docs/guides/qdrant/ops-request/overview.md) for operation links. diff --git a/docs/guides/qdrant/quickstart/quickstart.md b/docs/guides/qdrant/quickstart/quickstart.md index 6b4d7912c..c5a64237b 100644 --- a/docs/guides/qdrant/quickstart/quickstart.md +++ b/docs/guides/qdrant/quickstart/quickstart.md @@ -18,11 +18,11 @@ This tutorial will show you how to use KubeDB to run a Qdrant database. ## Before You Begin -At first, you need to have a Kubernetes cluster, and the `kubectl` command-line tool must be configured to communicate with your cluster. If you do not already have a cluster, you can create one by using [kind](https://kind.sigs.k8s.io/docs/user/quick-start/). +- At first, you need to have a Kubernetes cluster, and the `kubectl` command-line tool must be configured to communicate with your cluster. If you do not already have a cluster, you can create one by using [kind](https://kind.sigs.k8s.io/docs/user/quick-start/). -Now, install KubeDB cli on your workstation and KubeDB operator in your cluster following the steps [here](/docs/setup/README.md). +- Now, install KubeDB operator in your cluster following the steps [here](/docs/setup/README.md) and make sure install with helm command including `--set global.featureGates.Qdrant=true` to ensure MSSQLServer CRD installation. -To keep things isolated, this tutorial uses a separate namespace called `demo` throughout this tutorial. +- To keep things isolated, this tutorial uses a separate namespace called `demo` throughout this tutorial. ```bash $ kubectl create ns demo @@ -53,7 +53,6 @@ NAME VERSION DB_IMAGE DEPRECATED A 1.15.4 1.15.4 docker.io/qdrant/qdrant:v1.15.4-unprivileged 13d 1.16.2 1.16.2 docker.io/qdrant/qdrant:v1.16.2-unprivileged 13d 1.17.0 1.17.0 docker.io/qdrant/qdrant:v1.17.0-unprivileged 13d - ``` Notice the `DEPRECATED` column. `true` means that QdrantVersion is deprecated for the current KubeDB version and KubeDB will not work for that version. @@ -74,7 +73,7 @@ metadata: namespace: demo spec: version: "1.17.0" - mode: "standalone" + mode: "Standalone" storage: storageClassName: "standard" accessModes: @@ -82,7 +81,7 @@ spec: resources: requests: storage: 1Gi - deletionPolicy: DoNotTerminate + deletionPolicy: WipeOut ``` ```bash @@ -93,7 +92,7 @@ qdrant.kubedb.com/qdrant-sample created Here, - `spec.version` is the name of the QdrantVersion CR where Docker images are specified. In this tutorial, a Qdrant `1.17.0` cluster is created. -- `spec.replicas` specifies the number of Qdrant nodes in the cluster. +- `spec.mode` specifies the Qdrant deployment mode `Standalone` or `Distributed`. - `spec.storage` specifies the size and StorageClass of the PVC that will be dynamically allocated to store data for each Qdrant pod. This storage spec will be passed to the StatefulSet created by KubeDB operator to run database pods. - `spec.deletionPolicy` specifies what KubeDB should do when a user tries to delete the `Qdrant` CR. Deletion policy `DoNotTerminate` prevents deletion of this object if the admission webhook is enabled. @@ -167,6 +166,126 @@ Status: Phase: Ready ``` +## Find Underlying Kubernetes Resources + +KubeDB operator creates a StatefulSet, PVCs, PVs, and Services for the Qdrant database. Let's check them: + +```bash +$ kubectl get statefulset -n demo qdrant-sample +NAME READY AGE +qdrant-sample 3/3 15m + +$ kubectl get pvc -n demo +NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE +data-qdrant-sample-0 Bound pvc-ccbba9d2-5556-49cd-9ce8-23c28ad56f12 1Gi RWO standard 15m +data-qdrant-sample-1 Bound pvc-ccbba9d2-5556-49cd-9ce8-23c28ad56f13 1Gi RWO standard 15m +data-qdrant-sample-2 Bound pvc-ccbba9d2-5556-49cd-9ce8-23c28ad56f14 1Gi RWO standard 15m + +$ kubectl get pv -n demo +NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE +pvc-ccbba9d2-5556-49cd-9ce8-23c28ad56f12 1Gi RWO Delete Bound demo/data-qdrant-sample-0 standard 15m +pvc-ccbba9d2-5556-49cd-9ce8-23c28ad56f13 1Gi RWO Delete Bound demo/data-qdrant-sample-1 standard 15m +pvc-ccbba9d2-5556-49cd-9ce8-23c28ad56f14 1Gi RWO Delete Bound demo/data-qdrant-sample-2 standard 15m + +$ kubectl get service -n demo +NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE +qdrant-sample ClusterIP 10.96.128.61 6333/TCP 15m +qdrant-sample-pods ClusterIP None 6333/TCP 15m +``` + +## Verify Qdrant YAML Output + +KubeDB operator sets the `status.phase` to `Ready` once the database is successfully created and is able to accept client connections. Run the following command to see the modified Qdrant object: + +```bash +$ kubectl get qdrant -n demo qdrant-sample -o yaml +``` + +```yaml +apiVersion: kubedb.com/v1alpha2 +kind: Qdrant +metadata: + creationTimestamp: "2025-06-01T10:00:00Z" + finalizers: + - kubedb.com + generation: 2 + name: qdrant-sample + namespace: demo + resourceVersion: "225923" + uid: e5c9292b-f3a3-4dbf-95c8-1b544096e1d4 +spec: + authSecret: + kind: Secret + name: qdrant-sample-auth + deletionPolicy: DoNotTerminate + healthChecker: + failureThreshold: 1 + periodSeconds: 10 + timeoutSeconds: 10 + podTemplate: + controller: {} + spec: + containers: + - name: qdrant + resources: + limits: + memory: 2Gi + requests: + cpu: 500m + memory: 2Gi + initContainers: + - name: qdrant-init + resources: + limits: + memory: 512Mi + requests: + cpu: 200m + memory: 512Mi + replicas: 3 + storage: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 1Gi + storageClassName: standard + storageType: Durable + version: "1.17.0" +status: + conditions: + - lastTransitionTime: "2025-06-01T10:00:00Z" + message: 'The KubeDB operator has started the provisioning of Qdrant: demo/qdrant-sample' + observedGeneration: 2 + reason: DatabaseProvisioningStartedSuccessfully + status: "True" + type: ProvisioningStarted + - lastTransitionTime: "2025-06-01T10:01:30Z" + message: All replicas are ready for Qdrant demo/qdrant-sample + observedGeneration: 2 + reason: AllReplicasReady + status: "True" + type: ReplicaReady + - lastTransitionTime: "2025-06-01T10:02:00Z" + message: database demo/qdrant-sample is accepting connection + observedGeneration: 2 + reason: AcceptingConnection + status: "True" + type: AcceptingConnection + - lastTransitionTime: "2025-06-01T10:02:00Z" + message: database demo/qdrant-sample is ready + observedGeneration: 2 + reason: AllReplicasReady + status: "True" + type: Ready + - lastTransitionTime: "2025-06-01T10:02:00Z" + message: 'The Qdrant: demo/qdrant-sample is successfully provisioned.' + observedGeneration: 2 + reason: DatabaseSuccessfullyProvisioned + status: "True" + type: Provisioned + phase: Ready +``` + ## Connect to Qdrant KubeDB creates a Secret containing authentication credentials for the Qdrant cluster. Let's check it: @@ -193,15 +312,128 @@ $ curl -H "api-key: $QDRANT_API_KEY" http://localhost:6333/collections {"result":{"collections":[]},"status":"ok","time":0.001} ``` +## AppBinding + +KubeDB creates an [AppBinding](/docs/guides/qdrant/concepts/appbinding.md) CR that holds the necessary information to connect with the database. + +```bash +$ kubectl get appbinding -n demo -o yaml +``` + +```yaml +apiVersion: v1 +items: + - apiVersion: appcatalog.appscode.com/v1alpha1 + kind: AppBinding + metadata: + creationTimestamp: "2025-06-01T10:00:30Z" + generation: 1 + labels: + app.kubernetes.io/component: database + app.kubernetes.io/instance: qdrant-sample + app.kubernetes.io/managed-by: kubedb.com + app.kubernetes.io/name: qdrants.kubedb.com + name: qdrant-sample + namespace: demo + ownerReferences: + - apiVersion: kubedb.com/v1alpha2 + blockOwnerDeletion: true + controller: true + kind: Qdrant + name: qdrant-sample + uid: e5c9292b-f3a3-4dbf-95c8-1b544096e1d4 + resourceVersion: "225711" + uid: 4d111a65-cf3d-4a74-a77e-24f2dee690df + spec: + appRef: + apiGroup: kubedb.com + kind: Qdrant + name: qdrant-sample + namespace: demo + clientConfig: + service: + name: qdrant-sample + path: / + port: 6333 + scheme: http + secret: + name: qdrant-sample-auth + type: kubedb.com/qdrant + version: "1.17" +kind: List +metadata: + resourceVersion: "" +``` + +You can use this AppBinding to connect with the Qdrant cluster from external applications. + ## Database DeletionPolicy -This tutorial has set `deletionPolicy: DoNotTerminate`. This will prevent you from deleting the database. If you try to delete it, you will get an error. Once you are done experimenting, change the `deletionPolicy` to `WipeOut` before deleting the Qdrant CR: +This field regulates the deletion process of the related resources when the `Qdrant` object is deleted. The available options are: + +**DoNotTerminate:** + +When `deletionPolicy` is set to `DoNotTerminate`, KubeDB prevents deletion of the database using admission webhooks. If you try to delete it, you will get an error: + +```bash +$ kubectl patch -n demo qdrant/qdrant-sample -p '{"spec":{"deletionPolicy":"DoNotTerminate"}}' --type="merge" +qdrant.kubedb.com/qdrant-sample patched + +$ kubectl delete qdrant -n demo qdrant-sample +The Qdrant "qdrant-sample" is invalid: spec.deletionPolicy: Invalid value: "qdrant-sample": Can not delete as deletionPolicy is set to "DoNotTerminate" +``` + +**Halt:** + +When `deletionPolicy` is set to `Halt`, KubeDB deletes the `Qdrant` object and its pods but keeps the `PVCs`, `Secrets`, and backup snapshots intact. This allows you to recreate the database later using the same data. + +At first, set the `deletionPolicy` to `Halt` and then delete the database: + +```bash +$ kubectl patch -n demo qdrant/qdrant-sample -p '{"spec":{"deletionPolicy":"Halt"}}' --type="merge" +qdrant.kubedb.com/qdrant-sample patched + +$ kubectl delete qdrant -n demo qdrant-sample +qdrant.kubedb.com "qdrant-sample" deleted +``` + +Now, check that the PVCs and Secrets still exist: + +```bash +$ kubectl get secret,pvc -n demo +NAME TYPE DATA AGE +secret/qdrant-sample-auth Opaque 2 30m + +NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE +persistentvolumeclaim/data-qdrant-sample-0 Bound pvc-ccbba9d2-5556-49cd-9ce8-23c28ad56f12 1Gi RWO standard 29m +persistentvolumeclaim/data-qdrant-sample-1 Bound pvc-ccbba9d2-5556-49cd-9ce8-23c28ad56f13 1Gi RWO standard 29m +persistentvolumeclaim/data-qdrant-sample-2 Bound pvc-ccbba9d2-5556-49cd-9ce8-23c28ad56f14 1Gi RWO standard 29m +``` + +You can recreate your Qdrant database later using these PVCs and Secrets. + +**Delete:** + +When `deletionPolicy` is set to `Delete`, KubeDB deletes the `Qdrant` object, pods, and `PVCs` but keeps the `Secrets` and backup snapshots. This allows you to restore the database from a previously taken backup. + +**WipeOut:** + +When `deletionPolicy` is set to `WipeOut`, KubeDB deletes all resources of this database (pods, PVCs, Secrets, snapshots, etc.). There is no option to recreate the database once deleted with this policy. ```bash $ kubectl patch -n demo qdrant/qdrant-sample -p '{"spec":{"deletionPolicy":"WipeOut"}}' --type="merge" qdrant.kubedb.com/qdrant-sample patched ``` +> Be careful when using `WipeOut` — there is no way to recover the database after deletion. + +## Next Steps + +- Learn about [backup and restore](/docs/guides/qdrant/backup/overview/index.md) Qdrant using KubeStash. +- Want to set up distributed Qdrant deployment? Check [Distributed Deployment](/docs/guides/qdrant/distributed-deployment/overview.md). +- Detail concepts of [Qdrant object](/docs/guides/qdrant/concepts/qdrant.md). +- Want to hack on KubeDB? Check our [contribution guidelines](/docs/CONTRIBUTING.md). + ## Cleaning up To clean up the Kubernetes resources created by this tutorial, run: diff --git a/docs/guides/qdrant/reconfigure-tls/reconfigure-tls.md b/docs/guides/qdrant/reconfigure-tls/reconfigure-tls.md index 8e668d4a5..517b5b95e 100644 --- a/docs/guides/qdrant/reconfigure-tls/reconfigure-tls.md +++ b/docs/guides/qdrant/reconfigure-tls/reconfigure-tls.md @@ -146,6 +146,8 @@ spec: - localhost ipAddresses: - "127.0.0.1" + timeout: 5m + apply: IfReady ``` Here, @@ -154,6 +156,8 @@ Here, - `spec.type` specifies that we are performing `ReconfigureTLS` on our database. - `spec.tls.issuerRef` specifies the issuer to use for signing certificates. - `spec.tls.certificates` specifies the certificate configuration. +- `spec.timeout` specifies the timeout for the operation (learn more [here](/docs/guides/qdrant/concepts/opsrequest.md#spectimeout)). +- `spec.apply` specifies when to apply the operation (learn more [here](/docs/guides/qdrant/concepts/opsrequest.md#specapply)). ```bash $ kubectl apply -f https://github.com/kubedb/docs/raw/{{< param "info.version" >}}/docs/guides/qdrant/reconfigure-tls/yamls/add-tls.yaml @@ -240,6 +244,12 @@ NAME TYPE STATUS AGE qdops-remove-tls ReconfigureTLS Successful 4m20s ``` +## Next Steps + +- Learn about [backup and restore](/docs/guides/qdrant/backup/overview/index.md) Qdrant using KubeStash. +- Detail concepts of [Qdrant object](/docs/guides/qdrant/concepts/qdrant.md). +- Want to hack on KubeDB? Check our [contribution guidelines](/docs/CONTRIBUTING.md). + ## Cleaning up To clean up the Kubernetes resources created by this tutorial, run: diff --git a/docs/guides/qdrant/reconfigure/reconfigure.md b/docs/guides/qdrant/reconfigure/reconfigure.md index 85fe6db61..c8362dcca 100644 --- a/docs/guides/qdrant/reconfigure/reconfigure.md +++ b/docs/guides/qdrant/reconfigure/reconfigure.md @@ -132,6 +132,8 @@ spec: configuration: configSecret: name: new-qdrant-configuration + timeout: 5m + apply: IfReady ``` Here, @@ -139,6 +141,8 @@ Here, - `spec.databaseRef.name` specifies that we are reconfiguring `qdrant-sample` database. - `spec.type` specifies that we are performing `Reconfigure` on our database. - `spec.configuration.configSecret.name` specifies the name of the new configuration secret. +- `spec.timeout` specifies the timeout for the operation (learn more [here](/docs/guides/qdrant/concepts/opsrequest.md#spectimeout)). +- `spec.apply` specifies when to apply the operation (learn more [here](/docs/guides/qdrant/concepts/opsrequest.md#specapply)). Let's create the `QdrantOpsRequest` CR we have shown above: @@ -181,7 +185,7 @@ spec: max_search_threads: 6 ``` -> **Note:** You can modify multiple fields of your current configuration using `applyConfig`. If you don't have any existing config secret, `applyConfig` will create a new secret for you. +> **Note:** You can modify multiple fields of your current configuration using `applyConfig`. If you don't have any existing config secret, `applyConfig` will create a new secret for you. If a config secret already exists, `applyConfig` will merge the new configuration with the existing one. Here, @@ -237,6 +241,12 @@ qdops-reconfigure-remove Reconfigure Successful 2m10s After this, the `Qdrant` CR will no longer reference a `configSecret` and the database will use its default configuration. +## Next Steps + +- Learn about [backup and restore](/docs/guides/qdrant/backup/overview/index.md) Qdrant using KubeStash. +- Detail concepts of [Qdrant object](/docs/guides/qdrant/concepts/qdrant.md). +- Want to hack on KubeDB? Check our [contribution guidelines](/docs/CONTRIBUTING.md). + ## Cleaning up To clean up the Kubernetes resources created by this tutorial, run: diff --git a/docs/guides/qdrant/restart/restart.md b/docs/guides/qdrant/restart/restart.md index a7e23d106..f5c0288d8 100644 --- a/docs/guides/qdrant/restart/restart.md +++ b/docs/guides/qdrant/restart/restart.md @@ -154,6 +154,12 @@ status: phase: Successful ``` +## Next Steps + +- Learn about [backup and restore](/docs/guides/qdrant/backup/overview/index.md) Qdrant using KubeStash. +- Detail concepts of [Qdrant object](/docs/guides/qdrant/concepts/qdrant.md). +- Want to hack on KubeDB? Check our [contribution guidelines](/docs/CONTRIBUTING.md). + ## Cleaning up To cleanup the Kubernetes resources created by this tutorial, run: diff --git a/docs/guides/qdrant/rotate-auth/rotateauth.md b/docs/guides/qdrant/rotate-auth/rotateauth.md index b2f44ee26..955020467 100644 --- a/docs/guides/qdrant/rotate-auth/rotateauth.md +++ b/docs/guides/qdrant/rotate-auth/rotateauth.md @@ -16,6 +16,10 @@ section_menu_id: guides KubeDB supports rotating authentication credentials (API key) for Qdrant via a `QdrantOpsRequest`. This tutorial will show you how to use KubeDB to rotate authentication credentials. +KubeDB supports two methods for rotating credentials: +- **Operator Generated** — KubeDB generates a new API key automatically. +- **User Defined** — You provide a custom secret with a new API key. + ## Before You Begin - At first, you need to have a Kubernetes cluster, and the `kubectl` command-line tool must be configured to communicate with your cluster. If you do not already have a cluster, you can create one by using [kind](https://kind.sigs.k8s.io/docs/user/quick-start/). @@ -93,12 +97,16 @@ spec: type: RotateAuth databaseRef: name: qdrant-sample + timeout: 5m + apply: IfReady ``` Here, - `spec.databaseRef.name` specifies that we are rotating auth credentials for `qdrant-sample` Qdrant database. - `spec.type` specifies that we are performing `RotateAuth` on our database. +- `spec.timeout` specifies the timeout for the operation (learn more [here](/docs/guides/qdrant/concepts/opsrequest.md#spectimeout)). +- `spec.apply` specifies when to apply the operation (learn more [here](/docs/guides/qdrant/concepts/opsrequest.md#specapply)). Let's create the `QdrantOpsRequest` CR we have shown above: @@ -154,12 +162,19 @@ $ kubectl apply -f https://github.com/kubedb/docs/raw/{{< param "info.version" > qdrantopsrequest.ops.kubedb.com/qdops-rotate-auth-custom created ``` +## Next Steps + +- Learn about [backup and restore](/docs/guides/qdrant/backup/overview/index.md) Qdrant using KubeStash. +- Detail concepts of [Qdrant object](/docs/guides/qdrant/concepts/qdrant.md). +- Want to hack on KubeDB? Check our [contribution guidelines](/docs/CONTRIBUTING.md). + ## Cleaning up To clean up the Kubernetes resources created by this tutorial, run: ```bash kubectl delete qdrantopsrequest -n demo qdops-rotate-auth qdops-rotate-auth-custom +kubectl delete secret -n demo my-custom-auth-secret kubectl delete qdrant -n demo qdrant-sample kubectl delete ns demo ``` \ No newline at end of file diff --git a/docs/guides/qdrant/scaling/horizontal-scaling/scale-horizontally/index.md b/docs/guides/qdrant/scaling/horizontal-scaling/scale-horizontally/index.md index f03673ba8..9a23d0cd4 100644 --- a/docs/guides/qdrant/scaling/horizontal-scaling/scale-horizontally/index.md +++ b/docs/guides/qdrant/scaling/horizontal-scaling/scale-horizontally/index.md @@ -131,6 +131,8 @@ spec: name: qdrant-sample horizontalScaling: node: 5 + timeout: 5m + apply: IfReady ``` Here, @@ -138,6 +140,8 @@ Here, - `spec.databaseRef.name` specifies that we are performing horizontal scaling on `qdrant-sample` Qdrant database. - `spec.type` specifies that we are performing `HorizontalScaling` on our database. - `spec.horizontalScaling.node` specifies the desired number of nodes after scaling. +- `spec.timeout` specifies the timeout for the operation (learn more [here](/docs/guides/qdrant/concepts/opsrequest.md#spectimeout)). +- `spec.apply` specifies when to apply the operation (learn more [here](/docs/guides/qdrant/concepts/opsrequest.md#specapply)). Let's create the `QdrantOpsRequest` CR we have shown above: @@ -224,6 +228,12 @@ qdrant-sample-3 1/1 Running 0 6m We have successfully performed horizontal scaling on the Qdrant cluster. +## Next Steps + +- Learn about [backup and restore](/docs/guides/qdrant/backup/overview/index.md) Qdrant using KubeStash. +- Detail concepts of [Qdrant object](/docs/guides/qdrant/concepts/qdrant.md). +- Want to hack on KubeDB? Check our [contribution guidelines](/docs/CONTRIBUTING.md). + ## Cleaning Up To clean up the Kubernetes resources created by this tutorial, run: diff --git a/docs/guides/qdrant/scaling/vertical-scaling/scale-vertically/index.md b/docs/guides/qdrant/scaling/vertical-scaling/scale-vertically/index.md index 7def4d69c..d03d59f40 100644 --- a/docs/guides/qdrant/scaling/vertical-scaling/scale-vertically/index.md +++ b/docs/guides/qdrant/scaling/vertical-scaling/scale-vertically/index.md @@ -134,6 +134,8 @@ spec: limits: cpu: "1" memory: "2Gi" + timeout: 5m + apply: IfReady ``` Here, @@ -141,6 +143,8 @@ Here, - `spec.databaseRef.name` specifies that we are performing vertical scaling on `qdrant-sample` Qdrant database. - `spec.type` specifies that we are performing `VerticalScaling` on our database. - `spec.verticalScaling.node.resources` specifies the desired CPU and memory resources for the Qdrant nodes. +- `spec.timeout` specifies the timeout for the operation (learn more [here](/docs/guides/qdrant/concepts/opsrequest.md#spectimeout)). +- `spec.apply` specifies when to apply the operation (learn more [here](/docs/guides/qdrant/concepts/opsrequest.md#specapply)). Let's create the `QdrantOpsRequest` CR we have shown above: @@ -181,6 +185,12 @@ $ kubectl get pod -n demo qdrant-sample-0 -o json | jq '.spec.containers[0].reso You can see from the above output that the resources of the `qdrant-sample-0` pod have been updated successfully. All pods in the cluster will have the same updated resource configuration. +## Next Steps + +- Learn about [backup and restore](/docs/guides/qdrant/backup/overview/index.md) Qdrant using KubeStash. +- Detail concepts of [Qdrant object](/docs/guides/qdrant/concepts/qdrant.md). +- Want to hack on KubeDB? Check our [contribution guidelines](/docs/CONTRIBUTING.md). + ## Cleaning Up To clean up the Kubernetes resources created by this tutorial, run: diff --git a/docs/guides/qdrant/tls/configure/index.md b/docs/guides/qdrant/tls/configure/index.md index 29d556408..6b7225313 100644 --- a/docs/guides/qdrant/tls/configure/index.md +++ b/docs/guides/qdrant/tls/configure/index.md @@ -117,7 +117,10 @@ spec: Here, -- `spec.tls.issuerRef` refers to the `qdrant-ca-issuer` issuer. +- `spec.tls.issuerRef` refers to the `qdrant-ca-issuer` issuer. It has the following sub-fields: + - `apiGroup` — the API group of the issuer (e.g., `cert-manager.io`). + - `kind` — the kind of issuer (`Issuer` or `ClusterIssuer`). + - `name` — the name of the issuer. - `spec.tls.certificates` provides options to configure certificate renewal and keep-alive. You can find more details from [here](/docs/guides/qdrant/concepts/qdrant.md#tls). **Deploy Qdrant Cluster:** @@ -161,6 +164,12 @@ qdrant-tls-client-cert kubernetes.io/tls 3 3m The TLS certificates have been created and the Qdrant cluster is now configured to use TLS/SSL for both client connections and peer-to-peer communication. +## Next Steps + +- Learn about [backup and restore](/docs/guides/qdrant/backup/overview/index.md) Qdrant using KubeStash. +- Detail concepts of [Qdrant object](/docs/guides/qdrant/concepts/qdrant.md). +- Want to hack on KubeDB? Check our [contribution guidelines](/docs/CONTRIBUTING.md). + ## Cleaning up To clean up the Kubernetes resources created by this tutorial, run: diff --git a/docs/guides/qdrant/update-version/versionupgrading/index.md b/docs/guides/qdrant/update-version/versionupgrading/index.md index c6341896d..8a78e0aad 100644 --- a/docs/guides/qdrant/update-version/versionupgrading/index.md +++ b/docs/guides/qdrant/update-version/versionupgrading/index.md @@ -181,6 +181,8 @@ spec: targetVersion: "1.18.0" databaseRef: name: qdrant-sample + timeout: 5m + apply: IfReady ``` Here, @@ -188,6 +190,8 @@ Here, - `spec.databaseRef.name` specifies that we are performing operation on `qdrant-sample` Qdrant database. - `spec.type` specifies that we are going to perform `UpdateVersion` on our database. - `spec.updateVersion.targetVersion` specifies the expected version `1.18.0` after updating. +- `spec.timeout` specifies the timeout for the operation (learn more [here](/docs/guides/qdrant/concepts/opsrequest.md#spectimeout)). +- `spec.apply` specifies when to apply the operation (learn more [here](/docs/guides/qdrant/concepts/opsrequest.md#specapply)). Let's create the `QdrantOpsRequest` cr we have shown above: @@ -282,6 +286,12 @@ qdrant/qdrant:v1.18.0 You can see above that our `Qdrant` has been updated with the new version. It verifies that we have successfully updated our Qdrant instance. +## Next Steps + +- Learn about [backup and restore](/docs/guides/qdrant/backup/overview/index.md) Qdrant using KubeStash. +- Detail concepts of [Qdrant object](/docs/guides/qdrant/concepts/qdrant.md). +- Want to hack on KubeDB? Check our [contribution guidelines](/docs/CONTRIBUTING.md). + ## Cleaning Up To clean up the Kubernetes resources created by this tutorial, run: @@ -289,4 +299,5 @@ To clean up the Kubernetes resources created by this tutorial, run: ```bash kubectl delete qdrant -n demo qdrant-sample kubectl delete QdrantOpsRequest -n demo qdops-update-version +kubectl delete ns demo ``` \ No newline at end of file diff --git a/docs/guides/qdrant/volume-expansion/volume-expansion.md b/docs/guides/qdrant/volume-expansion/volume-expansion.md index 4fb37688c..0caf5b136 100644 --- a/docs/guides/qdrant/volume-expansion/volume-expansion.md +++ b/docs/guides/qdrant/volume-expansion/volume-expansion.md @@ -239,6 +239,12 @@ qdrant-sample-qdrant-sample-2 Bound pvc-c3d4e5f6-a7b8-9012-cdef-012345678902 The above output verifies that we have successfully expanded the volume of the Qdrant database. +## Next Steps + +- Learn about [backup and restore](/docs/guides/qdrant/backup/overview/index.md) Qdrant using KubeStash. +- Detail concepts of [Qdrant object](/docs/guides/qdrant/concepts/qdrant.md). +- Want to hack on KubeDB? Check our [contribution guidelines](/docs/CONTRIBUTING.md). + ## Cleaning Up To clean up the Kubernetes resources created by this tutorial, run: From fddfa575546809ca9283ac88fc1a762b967c1fa8 Mon Sep 17 00:00:00 2001 From: Abdullah Al Masood <98589987+al-masood@users.noreply.github.com> Date: Wed, 13 May 2026 16:23:59 +0600 Subject: [PATCH 07/10] file rename Signed-off-by: Abdullah Al Masood <98589987+al-masood@users.noreply.github.com> --- .../logical/examples/backupconfiguration.yaml | 0 .../backup/logical/examples/backupstorage.yaml | 0 .../qdrant/backup/logical/examples/minio.yaml | 0 .../logical/examples/qdrant-restored.yaml | 0 .../qdrant/backup/logical/examples/qdrant.yaml | 0 .../logical/examples/restoresession.yaml | 0 .../logical/examples/retentionpolicy.yaml | 0 .../logical/examples/storage-secret.yaml | 0 .../examples/backupconfiguration.yaml | 0 .../volumesnapshot/examples/backupstorage.yaml | 0 .../examples/qdrant-restored.yaml | 0 .../backup/volumesnapshot/examples/qdrant.yaml | 0 .../examples/restoresession.yaml | 0 .../examples/retentionpolicy.yaml | 0 .../examples/storage-secret.yaml | 0 .../examples/volumesnapshotclass.yaml | 0 .../{database.md => compute-autoscale.md} | 4 ++-- .../{database.md => storage-autoscale.md} | 4 ++-- docs/guides/qdrant/backup/logical/index.md | 18 +++++++++--------- .../qdrant/backup/volumesnapshot/index.md | 18 +++++++++--------- docs/guides/qdrant/concepts/autoscaler.md | 2 +- .../{rotateauth.md => rotate-auth.md} | 6 +++--- .../index.md => horizontal-scaling.md} | 4 ++-- .../index.md => vertical-scaling.md} | 4 ++-- .../{configure/index.md => configure-tls.md} | 4 ++-- .../index.md => update-version.md} | 6 +++--- 26 files changed, 35 insertions(+), 35 deletions(-) rename docs/{guides => examples}/qdrant/backup/logical/examples/backupconfiguration.yaml (100%) rename docs/{guides => examples}/qdrant/backup/logical/examples/backupstorage.yaml (100%) rename docs/{guides => examples}/qdrant/backup/logical/examples/minio.yaml (100%) rename docs/{guides => examples}/qdrant/backup/logical/examples/qdrant-restored.yaml (100%) rename docs/{guides => examples}/qdrant/backup/logical/examples/qdrant.yaml (100%) rename docs/{guides => examples}/qdrant/backup/logical/examples/restoresession.yaml (100%) rename docs/{guides => examples}/qdrant/backup/logical/examples/retentionpolicy.yaml (100%) rename docs/{guides => examples}/qdrant/backup/logical/examples/storage-secret.yaml (100%) rename docs/{guides => examples}/qdrant/backup/volumesnapshot/examples/backupconfiguration.yaml (100%) rename docs/{guides => examples}/qdrant/backup/volumesnapshot/examples/backupstorage.yaml (100%) rename docs/{guides => examples}/qdrant/backup/volumesnapshot/examples/qdrant-restored.yaml (100%) rename docs/{guides => examples}/qdrant/backup/volumesnapshot/examples/qdrant.yaml (100%) rename docs/{guides => examples}/qdrant/backup/volumesnapshot/examples/restoresession.yaml (100%) rename docs/{guides => examples}/qdrant/backup/volumesnapshot/examples/retentionpolicy.yaml (100%) rename docs/{guides => examples}/qdrant/backup/volumesnapshot/examples/storage-secret.yaml (100%) rename docs/{guides => examples}/qdrant/backup/volumesnapshot/examples/volumesnapshotclass.yaml (100%) rename docs/guides/qdrant/autoscaler/compute/{database.md => compute-autoscale.md} (98%) rename docs/guides/qdrant/autoscaler/storage/{database.md => storage-autoscale.md} (99%) rename docs/guides/qdrant/rotate-auth/{rotateauth.md => rotate-auth.md} (98%) rename docs/guides/qdrant/scaling/horizontal-scaling/{scale-horizontally/index.md => horizontal-scaling.md} (99%) rename docs/guides/qdrant/scaling/vertical-scaling/{scale-vertically/index.md => vertical-scaling.md} (99%) rename docs/guides/qdrant/tls/{configure/index.md => configure-tls.md} (99%) rename docs/guides/qdrant/update-version/{versionupgrading/index.md => update-version.md} (99%) diff --git a/docs/guides/qdrant/backup/logical/examples/backupconfiguration.yaml b/docs/examples/qdrant/backup/logical/examples/backupconfiguration.yaml similarity index 100% rename from docs/guides/qdrant/backup/logical/examples/backupconfiguration.yaml rename to docs/examples/qdrant/backup/logical/examples/backupconfiguration.yaml diff --git a/docs/guides/qdrant/backup/logical/examples/backupstorage.yaml b/docs/examples/qdrant/backup/logical/examples/backupstorage.yaml similarity index 100% rename from docs/guides/qdrant/backup/logical/examples/backupstorage.yaml rename to docs/examples/qdrant/backup/logical/examples/backupstorage.yaml diff --git a/docs/guides/qdrant/backup/logical/examples/minio.yaml b/docs/examples/qdrant/backup/logical/examples/minio.yaml similarity index 100% rename from docs/guides/qdrant/backup/logical/examples/minio.yaml rename to docs/examples/qdrant/backup/logical/examples/minio.yaml diff --git a/docs/guides/qdrant/backup/logical/examples/qdrant-restored.yaml b/docs/examples/qdrant/backup/logical/examples/qdrant-restored.yaml similarity index 100% rename from docs/guides/qdrant/backup/logical/examples/qdrant-restored.yaml rename to docs/examples/qdrant/backup/logical/examples/qdrant-restored.yaml diff --git a/docs/guides/qdrant/backup/logical/examples/qdrant.yaml b/docs/examples/qdrant/backup/logical/examples/qdrant.yaml similarity index 100% rename from docs/guides/qdrant/backup/logical/examples/qdrant.yaml rename to docs/examples/qdrant/backup/logical/examples/qdrant.yaml diff --git a/docs/guides/qdrant/backup/logical/examples/restoresession.yaml b/docs/examples/qdrant/backup/logical/examples/restoresession.yaml similarity index 100% rename from docs/guides/qdrant/backup/logical/examples/restoresession.yaml rename to docs/examples/qdrant/backup/logical/examples/restoresession.yaml diff --git a/docs/guides/qdrant/backup/logical/examples/retentionpolicy.yaml b/docs/examples/qdrant/backup/logical/examples/retentionpolicy.yaml similarity index 100% rename from docs/guides/qdrant/backup/logical/examples/retentionpolicy.yaml rename to docs/examples/qdrant/backup/logical/examples/retentionpolicy.yaml diff --git a/docs/guides/qdrant/backup/logical/examples/storage-secret.yaml b/docs/examples/qdrant/backup/logical/examples/storage-secret.yaml similarity index 100% rename from docs/guides/qdrant/backup/logical/examples/storage-secret.yaml rename to docs/examples/qdrant/backup/logical/examples/storage-secret.yaml diff --git a/docs/guides/qdrant/backup/volumesnapshot/examples/backupconfiguration.yaml b/docs/examples/qdrant/backup/volumesnapshot/examples/backupconfiguration.yaml similarity index 100% rename from docs/guides/qdrant/backup/volumesnapshot/examples/backupconfiguration.yaml rename to docs/examples/qdrant/backup/volumesnapshot/examples/backupconfiguration.yaml diff --git a/docs/guides/qdrant/backup/volumesnapshot/examples/backupstorage.yaml b/docs/examples/qdrant/backup/volumesnapshot/examples/backupstorage.yaml similarity index 100% rename from docs/guides/qdrant/backup/volumesnapshot/examples/backupstorage.yaml rename to docs/examples/qdrant/backup/volumesnapshot/examples/backupstorage.yaml diff --git a/docs/guides/qdrant/backup/volumesnapshot/examples/qdrant-restored.yaml b/docs/examples/qdrant/backup/volumesnapshot/examples/qdrant-restored.yaml similarity index 100% rename from docs/guides/qdrant/backup/volumesnapshot/examples/qdrant-restored.yaml rename to docs/examples/qdrant/backup/volumesnapshot/examples/qdrant-restored.yaml diff --git a/docs/guides/qdrant/backup/volumesnapshot/examples/qdrant.yaml b/docs/examples/qdrant/backup/volumesnapshot/examples/qdrant.yaml similarity index 100% rename from docs/guides/qdrant/backup/volumesnapshot/examples/qdrant.yaml rename to docs/examples/qdrant/backup/volumesnapshot/examples/qdrant.yaml diff --git a/docs/guides/qdrant/backup/volumesnapshot/examples/restoresession.yaml b/docs/examples/qdrant/backup/volumesnapshot/examples/restoresession.yaml similarity index 100% rename from docs/guides/qdrant/backup/volumesnapshot/examples/restoresession.yaml rename to docs/examples/qdrant/backup/volumesnapshot/examples/restoresession.yaml diff --git a/docs/guides/qdrant/backup/volumesnapshot/examples/retentionpolicy.yaml b/docs/examples/qdrant/backup/volumesnapshot/examples/retentionpolicy.yaml similarity index 100% rename from docs/guides/qdrant/backup/volumesnapshot/examples/retentionpolicy.yaml rename to docs/examples/qdrant/backup/volumesnapshot/examples/retentionpolicy.yaml diff --git a/docs/guides/qdrant/backup/volumesnapshot/examples/storage-secret.yaml b/docs/examples/qdrant/backup/volumesnapshot/examples/storage-secret.yaml similarity index 100% rename from docs/guides/qdrant/backup/volumesnapshot/examples/storage-secret.yaml rename to docs/examples/qdrant/backup/volumesnapshot/examples/storage-secret.yaml diff --git a/docs/guides/qdrant/backup/volumesnapshot/examples/volumesnapshotclass.yaml b/docs/examples/qdrant/backup/volumesnapshot/examples/volumesnapshotclass.yaml similarity index 100% rename from docs/guides/qdrant/backup/volumesnapshot/examples/volumesnapshotclass.yaml rename to docs/examples/qdrant/backup/volumesnapshot/examples/volumesnapshotclass.yaml diff --git a/docs/guides/qdrant/autoscaler/compute/database.md b/docs/guides/qdrant/autoscaler/compute/compute-autoscale.md similarity index 98% rename from docs/guides/qdrant/autoscaler/compute/database.md rename to docs/guides/qdrant/autoscaler/compute/compute-autoscale.md index 2de4c9b43..ccc558f65 100644 --- a/docs/guides/qdrant/autoscaler/compute/database.md +++ b/docs/guides/qdrant/autoscaler/compute/compute-autoscale.md @@ -2,8 +2,8 @@ title: Qdrant Compute Autoscaler menu: docs_{{ .version }}: - identifier: qdrant-autoscaler-compute-database - name: Database + identifier: qdrant-autoscaler-compute-description + name: Autoscale Compute Resources parent: qdrant-autoscaler-compute weight: 20 menu_name: docs_{{ .version }} diff --git a/docs/guides/qdrant/autoscaler/storage/database.md b/docs/guides/qdrant/autoscaler/storage/storage-autoscale.md similarity index 99% rename from docs/guides/qdrant/autoscaler/storage/database.md rename to docs/guides/qdrant/autoscaler/storage/storage-autoscale.md index 2dd712261..2e347b2bf 100644 --- a/docs/guides/qdrant/autoscaler/storage/database.md +++ b/docs/guides/qdrant/autoscaler/storage/storage-autoscale.md @@ -2,8 +2,8 @@ title: Qdrant Storage Autoscaler menu: docs_{{ .version }}: - identifier: qdrant-autoscaler-storage-database - name: Database + identifier: qdrant-autoscaler-storage-description + name: Autoscale Storage parent: qdrant-autoscaler-storage weight: 20 menu_name: docs_{{ .version }} diff --git a/docs/guides/qdrant/backup/logical/index.md b/docs/guides/qdrant/backup/logical/index.md index dcc5a08bd..ef68051d3 100644 --- a/docs/guides/qdrant/backup/logical/index.md +++ b/docs/guides/qdrant/backup/logical/index.md @@ -40,7 +40,7 @@ $ kubectl create ns demo namespace/demo created ``` -> **Note:** YAML files used in this tutorial are stored in [docs/guides/qdrant/backup/logical/examples](https://github.com/kubedb/docs/tree/{{< param "info.version" >}}/docs/guides/qdrant/backup/logical/examples) directory of [kubedb/docs](https://github.com/kubedb/docs) repository. +> **Note:** YAML files used in this tutorial are stored in [docs/examples/qdrant/backup/logical/examples](https://github.com/kubedb/docs/tree/{{< param "info.version" >}}/docs/examples/qdrant/backup/logical/examples) directory of [kubedb/docs](https://github.com/kubedb/docs) repository. ## Backup Qdrant @@ -79,7 +79,7 @@ spec: Create the above `Qdrant` CR, ```bash -$ kubectl apply -f https://github.com/kubedb/docs/raw/{{< param "info.version" >}}/docs/guides/qdrant/backup/logical/examples/qdrant.yaml +$ kubectl apply -f https://github.com/kubedb/docs/raw/{{< param "info.version" >}}/docs/examples/qdrant/backup/logical/examples/qdrant.yaml qdrant.kubedb.com/sample-qdrant created ``` @@ -158,7 +158,7 @@ We are going to store our backed up data into a MinIO bucket. We have to create Let's deploy MinIO in the `demo` namespace: ```bash -$ kubectl apply -f https://github.com/kubedb/docs/raw/{{< param "info.version" >}}/docs/guides/qdrant/backup/logical/examples/minio.yaml +$ kubectl apply -f https://github.com/kubedb/docs/raw/{{< param "info.version" >}}/docs/examples/qdrant/backup/logical/examples/minio.yaml deployment.apps/minio created service/minio created job.batch/minio-setup created @@ -177,7 +177,7 @@ minio-xxxxxxxxxx-xxxxx 1/1 Running 0 2m Create a secret with credentials to access the MinIO storage: ```bash -$ kubectl apply -f https://github.com/kubedb/docs/raw/{{< param "info.version" >}}/docs/guides/qdrant/backup/logical/examples/storage-secret.yaml +$ kubectl apply -f https://github.com/kubedb/docs/raw/{{< param "info.version" >}}/docs/examples/qdrant/backup/logical/examples/storage-secret.yaml secret/aws-secret created ``` @@ -211,7 +211,7 @@ spec: Apply the BackupStorage: ```bash -$ kubectl apply -f https://github.com/kubedb/docs/raw/{{< param "info.version" >}}/docs/guides/qdrant/backup/logical/examples/backupstorage.yaml +$ kubectl apply -f https://github.com/kubedb/docs/raw/{{< param "info.version" >}}/docs/examples/qdrant/backup/logical/examples/backupstorage.yaml backupstorage.storage.kubestash.com/minio-storage created ``` @@ -249,7 +249,7 @@ spec: Let's create the above `RetentionPolicy`, ```bash -$ kubectl apply -f https://github.com/kubedb/docs/raw/{{< param "info.version" >}}/docs/guides/qdrant/backup/logical/examples/retentionpolicy.yaml +$ kubectl apply -f https://github.com/kubedb/docs/raw/{{< param "info.version" >}}/docs/examples/qdrant/backup/logical/examples/retentionpolicy.yaml retentionpolicy.storage.kubestash.com/demo-retention created ``` @@ -310,7 +310,7 @@ Here, Let's create the `BackupConfiguration` CR that we have shown above, ```bash -$ kubectl apply -f https://github.com/kubedb/docs/raw/{{< param "info.version" >}}/docs/guides/qdrant/backup/logical/examples/backupconfiguration.yaml +$ kubectl apply -f https://github.com/kubedb/docs/raw/{{< param "info.version" >}}/docs/examples/qdrant/backup/logical/examples/backupconfiguration.yaml backupconfiguration.core.kubestash.com/sample-qdrant-backup created ``` @@ -422,7 +422,7 @@ spec: Let's create the above database, ```bash -$ kubectl apply -f https://github.com/kubedb/docs/raw/{{< param "info.version" >}}/docs/guides/qdrant/backup/logical/examples/qdrant-restored.yaml +$ kubectl apply -f https://github.com/kubedb/docs/raw/{{< param "info.version" >}}/docs/examples/qdrant/backup/logical/examples/qdrant-restored.yaml qdrant.kubedb.com/restored-qdrant created ``` @@ -467,7 +467,7 @@ spec: Let's create the RestoreSession CRD object we have shown above, ```bash -$ kubectl apply -f https://github.com/kubedb/docs/raw/{{< param "info.version" >}}/docs/guides/qdrant/backup/logical/examples/restoresession.yaml +$ kubectl apply -f https://github.com/kubedb/docs/raw/{{< param "info.version" >}}/docs/examples/qdrant/backup/logical/examples/restoresession.yaml restoresession.core.kubestash.com/restore-sample-qdrant created ``` diff --git a/docs/guides/qdrant/backup/volumesnapshot/index.md b/docs/guides/qdrant/backup/volumesnapshot/index.md index fee50588f..3d6503b2f 100644 --- a/docs/guides/qdrant/backup/volumesnapshot/index.md +++ b/docs/guides/qdrant/backup/volumesnapshot/index.md @@ -41,7 +41,7 @@ $ kubectl create ns demo namespace/demo created ``` -> **Note:** YAML files used in this tutorial are stored in [docs/guides/qdrant/backup/volumesnapshot/examples](https://github.com/kubedb/docs/tree/{{< param "info.version" >}}/docs/guides/qdrant/backup/volumesnapshot/examples) directory of [kubedb/docs](https://github.com/kubedb/docs) repository. +> **Note:** YAML files used in this tutorial are stored in [docs/examples/qdrant/backup/volumesnapshot/examples](https://github.com/kubedb/docs/tree/{{< param "info.version" >}}/docs/examples/qdrant/backup/volumesnapshot/examples) directory of [kubedb/docs](https://github.com/kubedb/docs) repository. ## Prepare Backup Infrastructure @@ -71,7 +71,7 @@ parameters: ``` ```bash -$ kubectl apply -f https://github.com/kubedb/docs/raw/{{< param "info.version" >}}/docs/guides/qdrant/backup/volumesnapshot/examples/volumesnapshotclass.yaml +$ kubectl apply -f https://github.com/kubedb/docs/raw/{{< param "info.version" >}}/docs/examples/qdrant/backup/volumesnapshot/examples/volumesnapshotclass.yaml volumesnapshotclass.snapshot.storage.k8s.io/longhorn-snapshot-vsc created ``` @@ -107,7 +107,7 @@ spec: Apply the BackupStorage: ```bash -$ kubectl apply -f https://github.com/kubedb/docs/raw/{{< param "info.version" >}}/docs/guides/qdrant/backup/volumesnapshot/examples/backupstorage.yaml +$ kubectl apply -f https://github.com/kubedb/docs/raw/{{< param "info.version" >}}/docs/examples/qdrant/backup/volumesnapshot/examples/backupstorage.yaml backupstorage.storage.kubestash.com/minio-storage created ``` @@ -116,7 +116,7 @@ backupstorage.storage.kubestash.com/minio-storage created Create a secret with credentials to access the storage: ```bash -$ kubectl apply -f https://github.com/kubedb/docs/raw/{{< param "info.version" >}}/docs/guides/qdrant/backup/volumesnapshot/examples/storage-secret.yaml +$ kubectl apply -f https://github.com/kubedb/docs/raw/{{< param "info.version" >}}/docs/examples/qdrant/backup/volumesnapshot/examples/storage-secret.yaml secret/aws-secret created ``` @@ -154,7 +154,7 @@ spec: Let's create the above `RetentionPolicy`, ```bash -$ kubectl apply -f https://github.com/kubedb/docs/raw/{{< param "info.version" >}}/docs/guides/qdrant/backup/volumesnapshot/examples/retentionpolicy.yaml +$ kubectl apply -f https://github.com/kubedb/docs/raw/{{< param "info.version" >}}/docs/examples/qdrant/backup/volumesnapshot/examples/retentionpolicy.yaml retentionpolicy.storage.kubestash.com/demo-retention created ``` @@ -189,7 +189,7 @@ spec: Create the above `Qdrant` CR, ```bash -$ kubectl apply -f https://github.com/kubedb/docs/raw/{{< param "info.version" >}}/docs/guides/qdrant/backup/volumesnapshot/examples/qdrant.yaml +$ kubectl apply -f https://github.com/kubedb/docs/raw/{{< param "info.version" >}}/docs/examples/qdrant/backup/volumesnapshot/examples/qdrant.yaml qdrant.kubedb.com/sample-qdrant created ``` @@ -291,7 +291,7 @@ Here, Let's create the `BackupConfiguration` CR that we have shown above, ```bash -$ kubectl apply -f https://github.com/kubedb/docs/raw/{{< param "info.version" >}}/docs/guides/qdrant/backup/volumesnapshot/examples/backupconfiguration.yaml +$ kubectl apply -f https://github.com/kubedb/docs/raw/{{< param "info.version" >}}/docs/examples/qdrant/backup/volumesnapshot/examples/backupconfiguration.yaml backupconfiguration.core.kubestash.com/sample-qdrant-backup created ``` @@ -375,7 +375,7 @@ spec: Let's create the above database, ```bash -$ kubectl apply -f https://github.com/kubedb/docs/raw/{{< param "info.version" >}}/docs/guides/qdrant/backup/volumesnapshot/examples/qdrant-restored.yaml +$ kubectl apply -f https://github.com/kubedb/docs/raw/{{< param "info.version" >}}/docs/examples/qdrant/backup/volumesnapshot/examples/qdrant-restored.yaml qdrant.kubedb.com/restored-qdrant created ``` @@ -420,7 +420,7 @@ spec: Let's create the RestoreSession CRD object we have shown above, ```bash -$ kubectl apply -f https://github.com/kubedb/docs/raw/{{< param "info.version" >}}/docs/guides/qdrant/backup/volumesnapshot/examples/restoresession.yaml +$ kubectl apply -f https://github.com/kubedb/docs/raw/{{< param "info.version" >}}/docs/examples/qdrant/backup/volumesnapshot/examples/restoresession.yaml restoresession.core.kubestash.com/restore-sample-qdrant created ``` diff --git a/docs/guides/qdrant/concepts/autoscaler.md b/docs/guides/qdrant/concepts/autoscaler.md index ebb6b0fc7..4abc4e1bb 100644 --- a/docs/guides/qdrant/concepts/autoscaler.md +++ b/docs/guides/qdrant/concepts/autoscaler.md @@ -111,4 +111,4 @@ A `QdrantAutoscaler` object has the following fields in the `spec` section: ## Next Steps - Read the [Qdrant autoscaler overview](/docs/guides/qdrant/autoscaler/overview.md). -- See the [compute autoscaler guide](/docs/guides/qdrant/autoscaler/compute/database.md) and [storage autoscaler guide](/docs/guides/qdrant/autoscaler/storage/database.md). \ No newline at end of file +- See the [compute autoscaler guide](/docs/guides/qdrant/autoscaler/compute/compute-autoscale.md) and [storage autoscaler guide](/docs/guides/qdrant/autoscaler/storage/storage-autoscale.md). \ No newline at end of file diff --git a/docs/guides/qdrant/rotate-auth/rotateauth.md b/docs/guides/qdrant/rotate-auth/rotate-auth.md similarity index 98% rename from docs/guides/qdrant/rotate-auth/rotateauth.md rename to docs/guides/qdrant/rotate-auth/rotate-auth.md index 955020467..2105cf8df 100644 --- a/docs/guides/qdrant/rotate-auth/rotateauth.md +++ b/docs/guides/qdrant/rotate-auth/rotate-auth.md @@ -2,10 +2,10 @@ title: Rotate Auth of Qdrant menu: docs_{{ .version }}: - identifier: qdrant-rotate-auth-cluster - name: Cluster + identifier: qdrant-rotate-auth-description + name: Rotate Auth parent: qdrant-rotate-auth - weight: 30 + weight: 20 menu_name: docs_{{ .version }} section_menu_id: guides --- diff --git a/docs/guides/qdrant/scaling/horizontal-scaling/scale-horizontally/index.md b/docs/guides/qdrant/scaling/horizontal-scaling/horizontal-scaling.md similarity index 99% rename from docs/guides/qdrant/scaling/horizontal-scaling/scale-horizontally/index.md rename to docs/guides/qdrant/scaling/horizontal-scaling/horizontal-scaling.md index 9a23d0cd4..69e7b4cfd 100644 --- a/docs/guides/qdrant/scaling/horizontal-scaling/scale-horizontally/index.md +++ b/docs/guides/qdrant/scaling/horizontal-scaling/horizontal-scaling.md @@ -2,10 +2,10 @@ title: Scale Qdrant Horizontally menu: docs_{{ .version }}: - identifier: qdrant-scale-horizontally + identifier: qdrant-horizontal-scaling-ops name: Scale Horizontally parent: qdrant-horizontal-scaling - weight: 10 + weight: 20 menu_name: docs_{{ .version }} section_menu_id: guides --- diff --git a/docs/guides/qdrant/scaling/vertical-scaling/scale-vertically/index.md b/docs/guides/qdrant/scaling/vertical-scaling/vertical-scaling.md similarity index 99% rename from docs/guides/qdrant/scaling/vertical-scaling/scale-vertically/index.md rename to docs/guides/qdrant/scaling/vertical-scaling/vertical-scaling.md index d03d59f40..0054563b2 100644 --- a/docs/guides/qdrant/scaling/vertical-scaling/scale-vertically/index.md +++ b/docs/guides/qdrant/scaling/vertical-scaling/vertical-scaling.md @@ -2,10 +2,10 @@ title: Scale Qdrant Vertically menu: docs_{{ .version }}: - identifier: qdrant-scale-vertically + identifier: qdrant-vertical-scaling-ops name: Scale Vertically parent: qdrant-vertical-scaling - weight: 10 + weight: 20 menu_name: docs_{{ .version }} section_menu_id: guides --- diff --git a/docs/guides/qdrant/tls/configure/index.md b/docs/guides/qdrant/tls/configure-tls.md similarity index 99% rename from docs/guides/qdrant/tls/configure/index.md rename to docs/guides/qdrant/tls/configure-tls.md index 6b7225313..09f86d5e7 100644 --- a/docs/guides/qdrant/tls/configure/index.md +++ b/docs/guides/qdrant/tls/configure-tls.md @@ -2,10 +2,10 @@ title: Configure TLS in Qdrant menu: docs_{{ .version }}: - identifier: qdrant-tls-configure + identifier: qdrant-tls-description name: Configure TLS parent: qdrant-tls - weight: 10 + weight: 20 menu_name: docs_{{ .version }} section_menu_id: guides --- diff --git a/docs/guides/qdrant/update-version/versionupgrading/index.md b/docs/guides/qdrant/update-version/update-version.md similarity index 99% rename from docs/guides/qdrant/update-version/versionupgrading/index.md rename to docs/guides/qdrant/update-version/update-version.md index 8a78e0aad..5b28298a0 100644 --- a/docs/guides/qdrant/update-version/versionupgrading/index.md +++ b/docs/guides/qdrant/update-version/update-version.md @@ -2,10 +2,10 @@ title: Update Qdrant Version menu: docs_{{ .version }}: - identifier: qdrant-version-upgrading - name: Version Upgrading + identifier: qdrant-update-version-ops + name: Update Version parent: qdrant-update-version - weight: 10 + weight: 20 menu_name: docs_{{ .version }} section_menu_id: guides --- From 0caa1f1b8f743e01cd3b0ec7507e2a37b31a846e Mon Sep 17 00:00:00 2001 From: Abdullah Al Masood <98589987+al-masood@users.noreply.github.com> Date: Fri, 15 May 2026 14:48:41 +0600 Subject: [PATCH 08/10] guides update Signed-off-by: Abdullah Al Masood <98589987+al-masood@users.noreply.github.com> --- .../qdrant/reconfigure/apply-config.yaml | 16 + .../reconfigure/configuration-secret.yaml | 12 + .../reconfigure/new-configuration-secret.yaml | 12 + docs/examples/qdrant/reconfigure/qdrant.yaml | 17 ++ .../reconfigure/reconfigure-using-secret.yaml | 14 + .../qdrant/reconfigure/remove-config.yaml | 11 + docs/examples/qdrant/restart/ops-request.yaml | 6 +- .../rotate-auth/custom-auth-secret.yaml | 8 + .../qdrant/rotate-auth/ops-custom.yaml | 12 + .../qdrant/rotate-auth/ops-request.yaml | 5 +- docs/examples/qdrant/rotate-auth/qdrant.yaml | 15 + .../horizontal-scaling/hscale-down.yaml | 11 + .../scaling/horizontal-scaling/hscale-up.yaml | 13 + .../scaling/horizontal-scaling/qdrant.yaml | 16 + .../scaling/vertical-scaling/qdrant.yaml | 16 + .../scaling/vertical-scaling/vscale.yaml | 20 ++ .../qdrant/update-version/ops-request.yaml | 6 +- .../qdrant/update-version/qdrant.yaml | 15 + .../qdrant/volume-expansion/ops-request.yaml | 6 +- .../qdrant/volume-expansion/qdrant.yaml | 16 + docs/guides/qdrant/README.md | 2 +- .../autoscaler/compute/compute-autoscale.md | 1 + .../qdrant/autoscaler/compute/overview.md | 58 ++-- docs/guides/qdrant/autoscaler/overview.md | 49 --- .../qdrant/autoscaler/storage/overview.md | 63 ++-- .../autoscaler/storage/storage-autoscale.md | 5 +- .../qdrant/backup/volumesnapshot/index.md | 84 ++---- docs/guides/qdrant/concepts/_index.md | 2 +- docs/guides/qdrant/concepts/catalog.md | 2 +- .../qdrant/configuration/using-config-file.md | 4 +- .../qdrant/distributed-deployment/overview.md | 4 +- .../images/qdrant-compute-autoscaling.png | Bin 0 -> 59037 bytes .../guides/qdrant/images/qdrant-lifecycle.png | Bin 96030 -> 156391 bytes .../images/qdrant-storage-autoscaling.png | Bin 0 -> 46114 bytes .../qdrant/images/qdrant-volume-expansion.png | Bin 0 -> 44681 bytes docs/guides/qdrant/quickstart/quickstart.md | 285 +++++++++++------- .../guides/qdrant/reconfigure-tls/overview.md | 2 +- docs/guides/qdrant/reconfigure/overview.md | 31 +- docs/guides/qdrant/reconfigure/reconfigure.md | 98 +++--- docs/guides/qdrant/restart/restart.md | 82 +++-- docs/guides/qdrant/rotate-auth/overview.md | 30 +- docs/guides/qdrant/rotate-auth/rotate-auth.md | 38 ++- .../horizontal-scaling/horizontal-scaling.md | 16 +- .../scaling/horizontal-scaling/overview.md | 35 ++- .../scaling/vertical-scaling/overview.md | 33 +- .../vertical-scaling/vertical-scaling.md | 8 +- docs/guides/qdrant/tls/overview.md | 6 +- docs/guides/qdrant/update-version/overview.md | 35 ++- .../qdrant/update-version/update-version.md | 170 +++++++---- .../qdrant/volume-expansion/overview.md | 13 +- .../volume-expansion/volume-expansion.md | 158 +++++++--- 51 files changed, 1021 insertions(+), 540 deletions(-) create mode 100644 docs/examples/qdrant/reconfigure/apply-config.yaml create mode 100644 docs/examples/qdrant/reconfigure/configuration-secret.yaml create mode 100644 docs/examples/qdrant/reconfigure/new-configuration-secret.yaml create mode 100644 docs/examples/qdrant/reconfigure/qdrant.yaml create mode 100644 docs/examples/qdrant/reconfigure/reconfigure-using-secret.yaml create mode 100644 docs/examples/qdrant/reconfigure/remove-config.yaml create mode 100644 docs/examples/qdrant/rotate-auth/custom-auth-secret.yaml create mode 100644 docs/examples/qdrant/rotate-auth/ops-custom.yaml create mode 100644 docs/examples/qdrant/rotate-auth/qdrant.yaml create mode 100644 docs/examples/qdrant/scaling/horizontal-scaling/hscale-down.yaml create mode 100644 docs/examples/qdrant/scaling/horizontal-scaling/hscale-up.yaml create mode 100644 docs/examples/qdrant/scaling/horizontal-scaling/qdrant.yaml create mode 100644 docs/examples/qdrant/scaling/vertical-scaling/qdrant.yaml create mode 100644 docs/examples/qdrant/scaling/vertical-scaling/vscale.yaml create mode 100644 docs/examples/qdrant/update-version/qdrant.yaml create mode 100644 docs/examples/qdrant/volume-expansion/qdrant.yaml delete mode 100644 docs/guides/qdrant/autoscaler/overview.md create mode 100644 docs/guides/qdrant/images/qdrant-compute-autoscaling.png create mode 100644 docs/guides/qdrant/images/qdrant-storage-autoscaling.png create mode 100644 docs/guides/qdrant/images/qdrant-volume-expansion.png diff --git a/docs/examples/qdrant/reconfigure/apply-config.yaml b/docs/examples/qdrant/reconfigure/apply-config.yaml new file mode 100644 index 000000000..0c2077147 --- /dev/null +++ b/docs/examples/qdrant/reconfigure/apply-config.yaml @@ -0,0 +1,16 @@ +apiVersion: ops.kubedb.com/v1alpha1 +kind: QdrantOpsRequest +metadata: + name: qdops-reconfigure-apply-config + namespace: demo +spec: + type: Reconfigure + databaseRef: + name: qdrant-sample + configuration: + applyConfig: + config.yaml: | + log_level: DEBUG + performance: + max_search_threads: 6 + update_rate_limit: 100 diff --git a/docs/examples/qdrant/reconfigure/configuration-secret.yaml b/docs/examples/qdrant/reconfigure/configuration-secret.yaml new file mode 100644 index 000000000..2a3176d41 --- /dev/null +++ b/docs/examples/qdrant/reconfigure/configuration-secret.yaml @@ -0,0 +1,12 @@ +apiVersion: v1 +stringData: + config.yaml: | + log_level: DEBUG + performance: + max_search_threads: 4 + update_rate_limit: 100 +kind: Secret +metadata: + name: qdrant-configuration + namespace: demo +type: Opaque diff --git a/docs/examples/qdrant/reconfigure/new-configuration-secret.yaml b/docs/examples/qdrant/reconfigure/new-configuration-secret.yaml new file mode 100644 index 000000000..836cce67f --- /dev/null +++ b/docs/examples/qdrant/reconfigure/new-configuration-secret.yaml @@ -0,0 +1,12 @@ +apiVersion: v1 +stringData: + config.yaml: | + log_level: DEBUG + performance: + max_search_threads: 8 + update_rate_limit: 100 +kind: Secret +metadata: + name: new-qdrant-configuration + namespace: demo +type: Opaque diff --git a/docs/examples/qdrant/reconfigure/qdrant.yaml b/docs/examples/qdrant/reconfigure/qdrant.yaml new file mode 100644 index 000000000..556ffb44d --- /dev/null +++ b/docs/examples/qdrant/reconfigure/qdrant.yaml @@ -0,0 +1,17 @@ +apiVersion: kubedb.com/v1alpha2 +kind: Qdrant +metadata: + name: qdrant-sample + namespace: demo +spec: + version: "1.17.0" + replicas: 3 + configSecret: + name: qdrant-configuration + storage: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 1Gi + deletionPolicy: WipeOut diff --git a/docs/examples/qdrant/reconfigure/reconfigure-using-secret.yaml b/docs/examples/qdrant/reconfigure/reconfigure-using-secret.yaml new file mode 100644 index 000000000..fb4fbfdf5 --- /dev/null +++ b/docs/examples/qdrant/reconfigure/reconfigure-using-secret.yaml @@ -0,0 +1,14 @@ +apiVersion: ops.kubedb.com/v1alpha1 +kind: QdrantOpsRequest +metadata: + name: qdops-reconfigure-config + namespace: demo +spec: + type: Reconfigure + databaseRef: + name: qdrant-sample + configuration: + configSecret: + name: new-qdrant-configuration + timeout: 5m + apply: IfReady diff --git a/docs/examples/qdrant/reconfigure/remove-config.yaml b/docs/examples/qdrant/reconfigure/remove-config.yaml new file mode 100644 index 000000000..ecc77fbb1 --- /dev/null +++ b/docs/examples/qdrant/reconfigure/remove-config.yaml @@ -0,0 +1,11 @@ +apiVersion: ops.kubedb.com/v1alpha1 +kind: QdrantOpsRequest +metadata: + name: qdops-reconfigure-remove + namespace: demo +spec: + type: Reconfigure + databaseRef: + name: qdrant-sample + configuration: + removeCustomConfig: true diff --git a/docs/examples/qdrant/restart/ops-request.yaml b/docs/examples/qdrant/restart/ops-request.yaml index 7fd92ce28..a354476bd 100644 --- a/docs/examples/qdrant/restart/ops-request.yaml +++ b/docs/examples/qdrant/restart/ops-request.yaml @@ -1,9 +1,11 @@ apiVersion: ops.kubedb.com/v1alpha1 kind: QdrantOpsRequest metadata: - name: qdrant-restart + name: qdops-restart namespace: demo spec: type: Restart databaseRef: - name: qdrant-sample \ No newline at end of file + name: qdrant-sample + timeout: 3m + apply: Always diff --git a/docs/examples/qdrant/rotate-auth/custom-auth-secret.yaml b/docs/examples/qdrant/rotate-auth/custom-auth-secret.yaml new file mode 100644 index 000000000..e9053a903 --- /dev/null +++ b/docs/examples/qdrant/rotate-auth/custom-auth-secret.yaml @@ -0,0 +1,8 @@ +apiVersion: v1 +stringData: + api-key: MyCus0mAPIKey +kind: Secret +metadata: + name: my-custom-auth-secret + namespace: demo +type: Opaque diff --git a/docs/examples/qdrant/rotate-auth/ops-custom.yaml b/docs/examples/qdrant/rotate-auth/ops-custom.yaml new file mode 100644 index 000000000..98b41cd32 --- /dev/null +++ b/docs/examples/qdrant/rotate-auth/ops-custom.yaml @@ -0,0 +1,12 @@ +apiVersion: ops.kubedb.com/v1alpha1 +kind: QdrantOpsRequest +metadata: + name: qdops-rotate-auth-custom + namespace: demo +spec: + type: RotateAuth + databaseRef: + name: qdrant-sample + authentication: + secretRef: + name: my-custom-auth-secret diff --git a/docs/examples/qdrant/rotate-auth/ops-request.yaml b/docs/examples/qdrant/rotate-auth/ops-request.yaml index 61d192a3d..0efac8951 100644 --- a/docs/examples/qdrant/rotate-auth/ops-request.yaml +++ b/docs/examples/qdrant/rotate-auth/ops-request.yaml @@ -1,10 +1,11 @@ apiVersion: ops.kubedb.com/v1alpha1 kind: QdrantOpsRequest metadata: - name: qdrant-rotate-auth + name: qdops-rotate-auth namespace: demo spec: type: RotateAuth databaseRef: name: qdrant-sample - timeout: 5m \ No newline at end of file + timeout: 5m + apply: IfReady diff --git a/docs/examples/qdrant/rotate-auth/qdrant.yaml b/docs/examples/qdrant/rotate-auth/qdrant.yaml new file mode 100644 index 000000000..549a356d3 --- /dev/null +++ b/docs/examples/qdrant/rotate-auth/qdrant.yaml @@ -0,0 +1,15 @@ +apiVersion: kubedb.com/v1alpha2 +kind: Qdrant +metadata: + name: qdrant-sample + namespace: demo +spec: + version: "1.17.0" + replicas: 3 + storage: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 1Gi + deletionPolicy: WipeOut diff --git a/docs/examples/qdrant/scaling/horizontal-scaling/hscale-down.yaml b/docs/examples/qdrant/scaling/horizontal-scaling/hscale-down.yaml new file mode 100644 index 000000000..d2cfa6d0c --- /dev/null +++ b/docs/examples/qdrant/scaling/horizontal-scaling/hscale-down.yaml @@ -0,0 +1,11 @@ +apiVersion: ops.kubedb.com/v1alpha1 +kind: QdrantOpsRequest +metadata: + name: qdops-hscale-down + namespace: demo +spec: + type: HorizontalScaling + databaseRef: + name: qdrant-sample + horizontalScaling: + node: 4 diff --git a/docs/examples/qdrant/scaling/horizontal-scaling/hscale-up.yaml b/docs/examples/qdrant/scaling/horizontal-scaling/hscale-up.yaml new file mode 100644 index 000000000..5a4fc6373 --- /dev/null +++ b/docs/examples/qdrant/scaling/horizontal-scaling/hscale-up.yaml @@ -0,0 +1,13 @@ +apiVersion: ops.kubedb.com/v1alpha1 +kind: QdrantOpsRequest +metadata: + name: qdops-hscale-up + namespace: demo +spec: + type: HorizontalScaling + databaseRef: + name: qdrant-sample + horizontalScaling: + node: 5 + timeout: 5m + apply: IfReady diff --git a/docs/examples/qdrant/scaling/horizontal-scaling/qdrant.yaml b/docs/examples/qdrant/scaling/horizontal-scaling/qdrant.yaml new file mode 100644 index 000000000..74d7dec35 --- /dev/null +++ b/docs/examples/qdrant/scaling/horizontal-scaling/qdrant.yaml @@ -0,0 +1,16 @@ +apiVersion: kubedb.com/v1alpha2 +kind: Qdrant +metadata: + name: qdrant-sample + namespace: demo +spec: + version: "1.17.0" + replicas: 3 + storage: + storageClassName: "standard" + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 1Gi + deletionPolicy: WipeOut diff --git a/docs/examples/qdrant/scaling/vertical-scaling/qdrant.yaml b/docs/examples/qdrant/scaling/vertical-scaling/qdrant.yaml new file mode 100644 index 000000000..74d7dec35 --- /dev/null +++ b/docs/examples/qdrant/scaling/vertical-scaling/qdrant.yaml @@ -0,0 +1,16 @@ +apiVersion: kubedb.com/v1alpha2 +kind: Qdrant +metadata: + name: qdrant-sample + namespace: demo +spec: + version: "1.17.0" + replicas: 3 + storage: + storageClassName: "standard" + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 1Gi + deletionPolicy: WipeOut diff --git a/docs/examples/qdrant/scaling/vertical-scaling/vscale.yaml b/docs/examples/qdrant/scaling/vertical-scaling/vscale.yaml new file mode 100644 index 000000000..c95242583 --- /dev/null +++ b/docs/examples/qdrant/scaling/vertical-scaling/vscale.yaml @@ -0,0 +1,20 @@ +apiVersion: ops.kubedb.com/v1alpha1 +kind: QdrantOpsRequest +metadata: + name: qdops-vscale + namespace: demo +spec: + type: VerticalScaling + databaseRef: + name: qdrant-sample + verticalScaling: + node: + resources: + requests: + cpu: "500m" + memory: "1Gi" + limits: + cpu: "1" + memory: "2Gi" + timeout: 5m + apply: IfReady diff --git a/docs/examples/qdrant/update-version/ops-request.yaml b/docs/examples/qdrant/update-version/ops-request.yaml index 25f4fc93e..666e99b07 100644 --- a/docs/examples/qdrant/update-version/ops-request.yaml +++ b/docs/examples/qdrant/update-version/ops-request.yaml @@ -1,11 +1,13 @@ apiVersion: ops.kubedb.com/v1alpha1 kind: QdrantOpsRequest metadata: - name: qdrant-update-version + name: qdops-update-version namespace: demo spec: type: UpdateVersion databaseRef: name: qdrant-sample updateVersion: - targetVersion: 1.18.0 \ No newline at end of file + targetVersion: "1.17.0" + timeout: 5m + apply: IfReady diff --git a/docs/examples/qdrant/update-version/qdrant.yaml b/docs/examples/qdrant/update-version/qdrant.yaml new file mode 100644 index 000000000..971c3a293 --- /dev/null +++ b/docs/examples/qdrant/update-version/qdrant.yaml @@ -0,0 +1,15 @@ +apiVersion: kubedb.com/v1alpha2 +kind: Qdrant +metadata: + name: qdrant-sample + namespace: demo +spec: + version: "1.16.2" + replicas: 3 + storage: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 1Gi + deletionPolicy: WipeOut diff --git a/docs/examples/qdrant/volume-expansion/ops-request.yaml b/docs/examples/qdrant/volume-expansion/ops-request.yaml index f2f022df7..d0f996d64 100644 --- a/docs/examples/qdrant/volume-expansion/ops-request.yaml +++ b/docs/examples/qdrant/volume-expansion/ops-request.yaml @@ -1,12 +1,12 @@ apiVersion: ops.kubedb.com/v1alpha1 kind: QdrantOpsRequest metadata: - name: qdrant-volume-expand + name: qdops-vol-exp namespace: demo spec: type: VolumeExpansion databaseRef: name: qdrant-sample volumeExpansion: - mode: Online - node: 4Gi \ No newline at end of file + mode: "Offline" + node: 3Gi diff --git a/docs/examples/qdrant/volume-expansion/qdrant.yaml b/docs/examples/qdrant/volume-expansion/qdrant.yaml new file mode 100644 index 000000000..1e644039a --- /dev/null +++ b/docs/examples/qdrant/volume-expansion/qdrant.yaml @@ -0,0 +1,16 @@ +apiVersion: kubedb.com/v1alpha2 +kind: Qdrant +metadata: + name: qdrant-sample + namespace: demo +spec: + version: "1.17.0" + replicas: 3 + storage: + storageClassName: "longhorn" + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 1Gi + deletionPolicy: WipeOut diff --git a/docs/guides/qdrant/README.md b/docs/guides/qdrant/README.md index 494f72f24..1ce6a6cb5 100644 --- a/docs/guides/qdrant/README.md +++ b/docs/guides/qdrant/README.md @@ -42,7 +42,7 @@ KubeDB supports the following Microsoft SQL Server Version. ## Life Cycle of a Qdrant Object

-  lifecycle +  lifecycle

diff --git a/docs/guides/qdrant/autoscaler/compute/compute-autoscale.md b/docs/guides/qdrant/autoscaler/compute/compute-autoscale.md index ccc558f65..d4ed02549 100644 --- a/docs/guides/qdrant/autoscaler/compute/compute-autoscale.md +++ b/docs/guides/qdrant/autoscaler/compute/compute-autoscale.md @@ -26,6 +26,7 @@ This guide will show you how to use `KubeDB` to auto-scale compute resources i.e - You should be familiar with the following `KubeDB` concepts: - [Qdrant](/docs/guides/qdrant/concepts/) + - [QdrantAutoscaler](/docs/guides/qdrant/concepts/autoscaler.md) - [QdrantOpsRequest](/docs/guides/qdrant/concepts/opsrequest.md) - [Compute Resource Autoscaling Overview](/docs/guides/qdrant/autoscaler/overview.md) diff --git a/docs/guides/qdrant/autoscaler/compute/overview.md b/docs/guides/qdrant/autoscaler/compute/overview.md index 1a74801e7..5248c7883 100644 --- a/docs/guides/qdrant/autoscaler/compute/overview.md +++ b/docs/guides/qdrant/autoscaler/compute/overview.md @@ -1,5 +1,5 @@ --- -title: Qdrant Compute Autoscaler Overview +title: Qdrant Compute Autoscaling Overview menu: docs_{{ .version }}: identifier: qdrant-autoscaler-compute-overview @@ -12,44 +12,44 @@ section_menu_id: guides > New to KubeDB? Please start [here](/docs/README.md). -# Qdrant Compute Autoscaler +# Qdrant Compute Resource Autoscaling -This guide shows how to configure compute autoscaling for Qdrant. +This guide will give an overview on how KubeDB Autoscaler operator autoscales the database compute resources i.e. cpu and memory using `QdrantAutoscaler` crd. ## Before You Begin -- Install KubeDB Autoscaler operator. -- Install metrics-server in your cluster. -- Use the example files from `docs/examples/qdrant/quickstart/distributed.yaml` and `docs/examples/qdrant/autoscaler/compute/autoscaler.yaml`. +- You should be familiar with the following `KubeDB` concepts: + - [Qdrant](/docs/guides/qdrant/concepts/) + - [QdrantOpsRequest](/docs/guides/qdrant/concepts/opsrequest.md) + - [QdrantAutoscaler](/docs/guides/qdrant/concepts/autoscaler.md) -```bash -kubectl create ns demo -``` +## How Compute Autoscaling Works -## Deploy Qdrant +The following diagram shows how KubeDB Autoscaler operator autoscales the resources of `Qdrant` database components. Open the image in a new tab to see the enlarged version. -```bash -kubectl apply -f https://github.com/kubedb/docs/raw/{{< param "info.version" >}}/docs/examples/qdrant/quickstart/distributed.yaml -kubectl get qdrant -n demo qdrant-sample -w -``` +
+  Compute Auto Scaling process of Qdrant +
Fig: Compute Auto Scaling process of Qdrant
+
-## Apply QdrantAutoscaler +The Auto Scaling process consists of the following steps: -```bash -kubectl apply -f https://github.com/kubedb/docs/raw/{{< param "info.version" >}}/docs/examples/qdrant/autoscaler/compute/autoscaler.yaml -``` +1. At first, a user creates a `Qdrant` Custom Resource Object (CRO). -## Verify +2. `KubeDB` Provisioner operator watches the `Qdrant` CRO. -```bash -kubectl get qdrantautoscaler -n demo qdrant-as-compute -kubectl describe qdrantautoscaler -n demo qdrant-as-compute -``` +3. When the operator finds a `Qdrant` CRO, it creates `PetSet` and related necessary stuff like secrets, services, etc. -## Cleaning up +4. Then, in order to set up autoscaling of the `Qdrant` database the user creates a `QdrantAutoscaler` CRO with desired configuration. -```bash -kubectl delete qdrantautoscaler -n demo qdrant-as-compute -kubectl delete qdrant -n demo qdrant-sample -kubectl delete ns demo -``` +5. `KubeDB` Autoscaler operator watches the `QdrantAutoscaler` CRO. + +6. `KubeDB` Autoscaler operator generates recommendation using the modified version of kubernetes [official recommender](https://github.com/kubernetes/autoscaler/tree/master/vertical-pod-autoscaler/pkg/recommender) for different components of the database, as specified in the `QdrantAutoscaler` CRO. + +7. If the generated recommendation doesn't match the current resources of the database, then `KubeDB` Autoscaler operator creates a `QdrantOpsRequest` CRO to scale the database to match the recommendation generated. + +8. `KubeDB` Ops-manager operator watches the `QdrantOpsRequest` CRO. + +9. Then the `KubeDB` Ops-manager operator will scale the database component vertically as specified on the `QdrantOpsRequest` CRO. + +In the next docs, we are going to show a step-by-step guide on Autoscaling of various Qdrant database using `QdrantAutoscaler` CRD. diff --git a/docs/guides/qdrant/autoscaler/overview.md b/docs/guides/qdrant/autoscaler/overview.md deleted file mode 100644 index a9efda082..000000000 --- a/docs/guides/qdrant/autoscaler/overview.md +++ /dev/null @@ -1,49 +0,0 @@ ---- -title: Qdrant Autoscaler Overview -menu: - docs_{{ .version }}: - identifier: qdrant-autoscaler-overview - name: Overview - parent: qdrant-autoscaler - weight: 10 -menu_name: docs_{{ .version }} -section_menu_id: guides ---- - -> New to KubeDB? Please start [here](/docs/README.md). - -# Qdrant Autoscaling Overview - -This guide will give an overview of how KubeDB autoscales `Qdrant` database resources — both compute (CPU and memory) and storage. - -## Before You Begin - -- You should be familiar with the following `KubeDB` concepts: - - [Qdrant](/docs/guides/qdrant/concepts/qdrant.md) - - [QdrantAutoscaler](/docs/guides/qdrant/concepts/autoscaler.md) - - [QdrantOpsRequest](/docs/guides/qdrant/concepts/opsrequest.md) - -## How Autoscaling Works - -KubeDB uses the `QdrantAutoscaler` CR to configure automatic scaling of Qdrant resources. There are two types of autoscaling supported: - -### Compute Autoscaling - -KubeDB leverages the [Kubernetes Vertical Pod Autoscaler (VPA)](https://github.com/kubernetes/autoscaler/tree/master/vertical-pod-autoscaler) to recommend compute resource adjustments. The process works as follows: - -1. The user creates a `QdrantAutoscaler` CR with `spec.compute` configured. -2. KubeDB creates a VPA resource for the `Qdrant` StatefulSet. -3. The VPA monitors resource usage and provides recommendations. -4. When the recommendation differs from the current resources by more than `resourceDiffPercentage`, KubeDB creates a `QdrantOpsRequest` with `type: VerticalScaling` to apply the recommended resources. -5. After the OpsRequest completes, the pods are running with the updated resource requests and limits. - -### Storage Autoscaling - -KubeDB monitors PVC usage to automatically expand storage. The process works as follows: - -1. The user creates a `QdrantAutoscaler` CR with `spec.storage` configured. -2. KubeDB monitors the PVC storage usage of the Qdrant pods. -3. When the disk usage exceeds the `usageThreshold` percentage, KubeDB creates a `QdrantOpsRequest` with `type: VolumeExpansion` to expand the storage by `scalingThreshold` percent. -4. After the OpsRequest completes, the PVCs are expanded to the new size. - -In the next docs, we are going to show step-by-step guides on compute and storage autoscaling for Qdrant databases. diff --git a/docs/guides/qdrant/autoscaler/storage/overview.md b/docs/guides/qdrant/autoscaler/storage/overview.md index a13a37c72..c5da67fb5 100644 --- a/docs/guides/qdrant/autoscaler/storage/overview.md +++ b/docs/guides/qdrant/autoscaler/storage/overview.md @@ -1,5 +1,5 @@ --- -title: Qdrant Storage Autoscaler Overview +title: Qdrant Storage Autoscaling Overview menu: docs_{{ .version }}: identifier: qdrant-autoscaler-storage-overview @@ -12,45 +12,48 @@ section_menu_id: guides > New to KubeDB? Please start [here](/docs/README.md). -# Qdrant Storage Autoscaler +# Qdrant Storage Autoscaling -This guide shows how to configure storage autoscaling for Qdrant. +This guide will give an overview on how KubeDB `Autoscaler` operator autoscales the database storage using `QdrantAutoscaler` CRD. ## Before You Begin -- StorageClass with `allowVolumeExpansion: true`. -- KubeDB Autoscaler operator installed. -- Use the example files from `docs/examples/qdrant/quickstart/distributed.yaml` and `docs/examples/qdrant/autoscaler/storage/autoscaler.yaml`. +- You should be familiar with the following `KubeDB` concepts: + - [Qdrant](/docs/guides/qdrant/concepts/) + - [QdrantAutoscaler](/docs/guides/qdrant/concepts/autoscaler.md) + - [QdrantOpsRequest](/docs/guides/qdrant/concepts/opsrequest.md) + -```bash -kubectl create ns demo -kubectl get storageclass -``` +## How Storage Autoscaling Works -## Deploy Qdrant +The following diagram shows how KubeDB Autoscaler operator autoscales the resources of `Qdrant`. Open the image in a new tab to see the enlarged version. -```bash -kubectl apply -f https://github.com/kubedb/docs/raw/{{< param "info.version" >}}/docs/examples/qdrant/quickstart/distributed.yaml -kubectl get qdrant -n demo qdrant-sample -w -``` +
+  Storage Auto Scaling process of Qdrant +
Fig: Storage Auto Scaling process of Qdrant
+
-## Apply QdrantAutoscaler -```bash -kubectl apply -f https://github.com/kubedb/docs/raw/{{< param "info.version" >}}/docs/examples/qdrant/autoscaler/storage/autoscaler.yaml -``` +The Auto Scaling process consists of the following steps: -## Verify +1. At first, a user creates a `Qdrant` Custom Resource Object (CRO). -```bash -kubectl get qdrantautoscaler -n demo qdrant-as-storage -kubectl describe qdrantautoscaler -n demo qdrant-as-storage -``` +2. `KubeDB` Provisioner operator watches the `Qdrant` CRO. -## Cleaning up +3. When the operator finds a `Qdrant` CRO, it creates required number of `PetSets` and related necessary stuff like secrets, services, etc. -```bash -kubectl delete qdrantautoscaler -n demo qdrant-as-storage -kubectl delete qdrant -n demo qdrant-sample -kubectl delete ns demo -``` +4. Each PetSet creates a Persistent Volumes according to the Volume Claim Template provided in the petset's configuration. + +5. Then, in order to set up storage autoscaling of the `Qdrant` database the user creates a `QdrantAutoscaler` CRO with desired configuration. + +6. `KubeDB` Autoscaler operator watches the `QdrantAutoscaler` CRO. + +7. `KubeDB` Autoscaler operator continuously watches persistent volumes of the databases to check if it exceeds the specified usage threshold. + +8. If the usage exceeds the specified usage threshold, then `KubeDB` Autoscaler operator creates a `QdrantOpsRequest` to expand the storage of the database. + +9. `KubeDB` Ops-manager operator watches the `QdrantOpsRequest` CRO. + +10. Then the `KubeDB` Ops-manager operator will expand the storage of the database as specified on the `QdrantOpsRequest` CRO. + +In the next docs, we are going to show a step-by-step guide on Autoscaling storage of Qdrant database using `QdrantAutoscaler` CRD. diff --git a/docs/guides/qdrant/autoscaler/storage/storage-autoscale.md b/docs/guides/qdrant/autoscaler/storage/storage-autoscale.md index 2e347b2bf..9958af7a9 100644 --- a/docs/guides/qdrant/autoscaler/storage/storage-autoscale.md +++ b/docs/guides/qdrant/autoscaler/storage/storage-autoscale.md @@ -30,6 +30,7 @@ This guide will show you how to use `KubeDB` to autoscale the storage of a Qdran - You should be familiar with the following `KubeDB` concepts: - [Qdrant](/docs/guides/qdrant/concepts/) + - [QdrantAutoscaler](/docs/guides/qdrant/concepts/autoscaler.md) - [QdrantOpsRequest](/docs/guides/qdrant/concepts/opsrequest.md) - [Storage Autoscaling Overview](/docs/guides/qdrant/autoscaler/overview.md) @@ -94,7 +95,7 @@ NAME VERSION STATUS AGE qdrant-db 1.17.0 Ready 3m46s ``` -Let's check the volume size from the StatefulSet and from the persistent volumes: +Let's check the volume size from the Petset and from the persistent volumes: ```bash $ kubectl get sts -n demo qdrant-db -o json | jq '.spec.volumeClaimTemplates[].spec.resources.requests.storage' @@ -107,7 +108,7 @@ pvc-4a509b05-774b-42d9-b36d-599c9056af37 1Gi RWO Delete pvc-c27eee12-cd86-4410-b39e-b1dd735fc14d 1Gi RWO Delete Bound demo/data-qdrant-db-1 topolvm-provisioner 57s ``` -You can see the StatefulSet has 1GB storage and the capacity of all the persistent volumes is also 1GB. +You can see the Petset has 1GB storage and the capacity of all the persistent volumes is also 1GB. We are now ready to apply the `QdrantAutoscaler` CRD to set up storage autoscaling for this database. diff --git a/docs/guides/qdrant/backup/volumesnapshot/index.md b/docs/guides/qdrant/backup/volumesnapshot/index.md index 3d6503b2f..5e738c9bb 100644 --- a/docs/guides/qdrant/backup/volumesnapshot/index.md +++ b/docs/guides/qdrant/backup/volumesnapshot/index.md @@ -20,19 +20,7 @@ KubeStash allows you to take volume snapshot backups of Qdrant databases. Volume - At first, you need to have a Kubernetes cluster, and the `kubectl` command-line tool must be configured to communicate with your cluster. If you do not already have a cluster, you can create one by using `Minikube` or `Kind`. - Install `KubeDB` in your cluster following the steps [here](/docs/setup/README.md). - Install `KubeStash` in your cluster following the steps [here](https://kubestash.com/docs/latest/setup/install/kubestash). -- Install KubeStash `kubectl` plugin following the steps [here](https://kubestash.com/docs/latest/setup/install/kubectl-plugin/). -- Ensure your storage provider supports VolumeSnapshots (e.g., Longhorn, AWS EBS, GCE PD). -- If you are not familiar with how KubeStash backup and restore Qdrant databases, please check the following guide [here](/docs/guides/qdrant/backup/overview/index.md). - -You should be familiar with the following `KubeStash` concepts: - -- [BackupStorage](https://kubestash.com/docs/latest/concepts/crds/backupstorage/) -- [BackupConfiguration](https://kubestash.com/docs/latest/concepts/crds/backupconfiguration/) -- [BackupSession](https://kubestash.com/docs/latest/concepts/crds/backupsession/) -- [RestoreSession](https://kubestash.com/docs/latest/concepts/crds/restoresession/) -- [Addon](https://kubestash.com/docs/latest/concepts/crds/addon/) -- [Function](https://kubestash.com/docs/latest/concepts/crds/function/) -- [Task](https://kubestash.com/docs/latest/concepts/crds/addon/#task-specification) +- To install `External-snapshotter` in your cluster following the steps [here](https://github.com/kubernetes-csi/external-snapshotter/tree/release-5.0). To keep things isolated, we are going to use a separate namespace called `demo` throughout this tutorial. @@ -43,65 +31,28 @@ namespace/demo created > **Note:** YAML files used in this tutorial are stored in [docs/examples/qdrant/backup/volumesnapshot/examples](https://github.com/kubedb/docs/tree/{{< param "info.version" >}}/docs/examples/qdrant/backup/volumesnapshot/examples) directory of [kubedb/docs](https://github.com/kubedb/docs) repository. -## Prepare Backup Infrastructure - -We are going to store our backed up data using VolumeSnapshots. We have to create a `VolumeSnapshotClass`, `Secret`, `BackupStorage`, and `RetentionPolicy` CR to use this backend. If you want to use a different backend, please read the respective backend configuration doc from [here](https://kubestash.com/docs/latest/guides/backends/overview/). - -### Ensure VolumeSnapshotClass - -First, ensure that the `VolumeSnapshotClass` for your storage provider is available. For Longhorn: - -```bash -$ kubectl get volumesnapshotclasses -NAME DRIVER DELETIONPOLICY AGE -longhorn-snapshot-vsc driver.longhorn.io Delete 7d22h -``` - -If not available, create one: - -```yaml -apiVersion: snapshot.storage.k8s.io/v1 -kind: VolumeSnapshotClass -metadata: - name: longhorn-snapshot-vsc -driver: driver.longhorn.io -deletionPolicy: Delete -parameters: - type: snap -``` - -```bash -$ kubectl apply -f https://github.com/kubedb/docs/raw/{{< param "info.version" >}}/docs/examples/qdrant/backup/volumesnapshot/examples/volumesnapshotclass.yaml -volumesnapshotclass.snapshot.storage.k8s.io/longhorn-snapshot-vsc created -``` - -Note: Ensure that the VolumeSnapshotClass is provisioned with the same storage class driver used for provisioning your Qdrant database. - -### Create BackupStorage - -Create a `BackupStorage` CR to configure the backup storage: +### BackupStorage +BackupStorage is a CR provided by KubeStash that can manage storage from various providers like GCS, S3, and more. ```yaml apiVersion: storage.kubestash.com/v1alpha1 kind: BackupStorage metadata: - name: minio-storage + name: storage namespace: demo spec: storage: provider: s3 s3: + endpoint: s3.amazonaws.com bucket: qdrant-backups - endpoint: http://minio.demo.svc:9000 - insecureTLS: true - prefix: backup/demo region: us-east-1 - secretName: aws-secret + prefix: backup-demo + secretName: s3-secret usagePolicy: allowedNamespaces: from: All - default: true - deletionPolicy: Delete + deletionPolicy: WipeOut ``` Apply the BackupStorage: @@ -110,14 +61,28 @@ Apply the BackupStorage: $ kubectl apply -f https://github.com/kubedb/docs/raw/{{< param "info.version" >}}/docs/examples/qdrant/backup/volumesnapshot/examples/backupstorage.yaml backupstorage.storage.kubestash.com/minio-storage created ``` +Note: Before applying this yaml, verify that a bucket named `qdrant-backups` is already created on your bucket provider. ### Create Storage Secret Create a secret with credentials to access the storage: +```yaml +apiVersion: v1 +kind: Secret +type: Opaque +metadata: + name: s3-secret + namespace: demo +stringData: + AWS_ACCESS_KEY_ID: "*************26CX" + AWS_SECRET_ACCESS_KEY: "************jj3lp" + AWS_ENDPOINT: s3.amazonaws.com +``` + ```bash $ kubectl apply -f https://github.com/kubedb/docs/raw/{{< param "info.version" >}}/docs/examples/qdrant/backup/volumesnapshot/examples/storage-secret.yaml -secret/aws-secret created +secret/s3-secret created ``` ### Create Encryption Secret @@ -141,10 +106,9 @@ Below is the YAML of the `RetentionPolicy` object that we are going to create, apiVersion: storage.kubestash.com/v1alpha1 kind: RetentionPolicy metadata: - name: demo-retention + name: qdrant-retention-policy namespace: demo spec: - default: true maxNumberOfSnapshots: 5 usagePolicy: allowedNamespaces: diff --git a/docs/guides/qdrant/concepts/_index.md b/docs/guides/qdrant/concepts/_index.md index 761f029b9..1701e7aa2 100644 --- a/docs/guides/qdrant/concepts/_index.md +++ b/docs/guides/qdrant/concepts/_index.md @@ -130,7 +130,7 @@ NAME VERSION DB_IMAGE DEPRECATED AGE ### spec.podTemplate -KubeDB allows providing a template for database pods through `spec.podTemplate`. KubeDB operator will pass the information provided in `spec.podTemplate` to the StatefulSet created for Qdrant database. Notable sub-fields include: +KubeDB allows providing a template for database pods through `spec.podTemplate`. KubeDB operator will pass the information provided in `spec.podTemplate` to the Petset created for Qdrant database. Notable sub-fields include: - `spec.podTemplate.spec.serviceAccountName` to provide a custom ServiceAccount. - `spec.podTemplate.spec.imagePullSecrets` to pull images from a private registry. diff --git a/docs/guides/qdrant/concepts/catalog.md b/docs/guides/qdrant/concepts/catalog.md index b4cd9cde3..c2cb65340 100644 --- a/docs/guides/qdrant/concepts/catalog.md +++ b/docs/guides/qdrant/concepts/catalog.md @@ -56,7 +56,7 @@ The default value of this field is `false`. If `spec.deprecated` is set to `true ### spec.db.image -`spec.db.image` is a required field that specifies the Docker image which will be used to create the StatefulSet by KubeDB operator to create the expected Qdrant database. +`spec.db.image` is a required field that specifies the Docker image which will be used to create the Petset by KubeDB operator to create the expected Qdrant database. ```bash $ kubectl get qdrantversions diff --git a/docs/guides/qdrant/configuration/using-config-file.md b/docs/guides/qdrant/configuration/using-config-file.md index 6e496e7a2..cf1c48641 100644 --- a/docs/guides/qdrant/configuration/using-config-file.md +++ b/docs/guides/qdrant/configuration/using-config-file.md @@ -103,9 +103,9 @@ spec: deletionPolicy: WipeOut ``` -Now, wait a few minutes. KubeDB operator will create the necessary PVC, StatefulSet, services, and secrets. If everything goes well, we will see that a pod with the name `custom-qdrant-0` has been created. +Now, wait a few minutes. KubeDB operator will create the necessary PVC, Petset, services, and secrets. If everything goes well, we will see that a pod with the name `custom-qdrant-0` has been created. -Check that the StatefulSet's pod is running: +Check that the Petset's pod is running: ```bash $ kubectl get pod -n demo custom-qdrant-0 diff --git a/docs/guides/qdrant/distributed-deployment/overview.md b/docs/guides/qdrant/distributed-deployment/overview.md index e6943b1f4..b12ddb5d8 100644 --- a/docs/guides/qdrant/distributed-deployment/overview.md +++ b/docs/guides/qdrant/distributed-deployment/overview.md @@ -14,6 +14,8 @@ section_menu_id: guides # Qdrant Distributed Deployment +Since version v0.8.0, Qdrant supports a distributed deployment mode where multiple Qdrant services communicate with each other to distribute data across peers, extending storage capabilities and increasing stability. In this mode, Qdrant uses the [Raft](https://raft.github.io/) consensus protocol to maintain consistency of cluster topology and collection structure, while sharding enables horizontal scaling by splitting collections across multiple nodes. Replication further enhances reliability by keeping copies of shards across the cluster. + This tutorial will show you how to deploy a Qdrant database in distributed mode using KubeDB. ## Before You Begin @@ -126,7 +128,7 @@ qdrant-sample-1 1/1 Running 0 2m qdrant-sample-2 1/1 Running 0 2m ``` -In distributed mode, Qdrant creates a StatefulSet with the specified number of replicas. +In distributed mode, Qdrant creates a Petset with the specified number of replicas. ## Horizontal Scaling diff --git a/docs/guides/qdrant/images/qdrant-compute-autoscaling.png b/docs/guides/qdrant/images/qdrant-compute-autoscaling.png new file mode 100644 index 0000000000000000000000000000000000000000..af6a33318fefd01a9418d5dd90d5ed4cb01625bc GIT binary patch literal 59037 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 literal 96030 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 U5F$}2!n)gAM&^cP`VXG}6VZTD$^ZZW diff --git a/docs/guides/qdrant/images/qdrant-storage-autoscaling.png b/docs/guides/qdrant/images/qdrant-storage-autoscaling.png new file mode 100644 index 0000000000000000000000000000000000000000..cd868481eba2141619debb239cd903d185d0d7e1 GIT binary patch literal 46114 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

*p~?@gz3`Bqs{bNmX6OW)>Lb5c{U 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

6cSChak2OpEqoOMI(ruZf#B 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 z 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 NFQXz|E@>3-e*iG1k0by9 literal 0 HcmV?d00001 diff --git a/docs/guides/qdrant/images/qdrant-volume-expansion.png b/docs/guides/qdrant/images/qdrant-volume-expansion.png new file mode 100644 index 0000000000000000000000000000000000000000..654e2b2a0988c5387a4f0b82576addfdd0823699 GIT binary patch literal 44681 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 **Note:** `spec.storage` section is used to create PVC for the database pods. Specify only `requests`, not `limits` — PVC does not resize automatically. @@ -121,12 +121,52 @@ Annotations: API Version: kubedb.com/v1alpha2 Kind: Qdrant Metadata: - ... + Creation Timestamp: 2026-05-14T08:43:57Z + Finalizers: + kubedb.com + Generation: 3 + Resource Version: 3259342 + UID: 42df526b-fba5-4d21-aea4-d20ae5f36f30 Spec: Auth Secret: - Name: qdrant-sample-auth - Deletion Policy: DoNotTerminate - Replicas: 3 + Active From: 2026-05-14T08:43:57Z + API Group: + Kind: Secret + Name: qdrant-sample-auth + Deletion Policy: WipeOut + Health Checker: + Failure Threshold: 3 + Period Seconds: 10 + Timeout Seconds: 10 + Mode: Standalone + Pod Template: + Controller: + Metadata: + Spec: + Containers: + Name: qdrant + Resources: + Limits: + Memory: 1Gi + Requests: + Cpu: 500m + Memory: 1Gi + Security Context: + Allow Privilege Escalation: false + Capabilities: + Drop: + ALL + Run As Group: 1000 + Run As Non Root: true + Run As User: 1000 + Seccomp Profile: + Type: RuntimeDefault + Pod Placement Policy: + Name: default + Security Context: + Fs Group: 1000 + Service Account Name: qdrant-sample + Replicas: 1 Storage: Access Modes: ReadWriteOnce @@ -138,59 +178,63 @@ Spec: Version: 1.17.0 Status: Conditions: - Last Transition Time: 2024-10-01T10:00:00Z - Message: The KubeDB operator has started the provisioning of Qdrant: demo/qdrant-sample + Last Transition Time: 2026-05-14T08:43:57Z + Message: The KubeDB operator has started the provisioning of Qdrant: demo qdrant-sample + Observed Generation: 1 Reason: DatabaseProvisioningStartedSuccessfully Status: True Type: ProvisioningStarted - Last Transition Time: 2024-10-01T10:01:30Z + Last Transition Time: 2026-05-14T08:44:11Z Message: All desired replicas are ready. + Observed Generation: 3 Reason: AllReplicasReady Status: True Type: ReplicaReady - Last Transition Time: 2024-10-01T10:02:00Z - Message: The Qdrant: demo/qdrant-sample is accepting client requests. - Reason: DatabaseAcceptingConnectionRequest + Last Transition Time: 2026-05-14T08:44:22Z + Message: database demo/qdrant-sample is accepting connection + Observed Generation: 3 + Reason: AcceptingConnection Status: True Type: AcceptingConnection - Last Transition Time: 2024-10-01T10:02:00Z - Message: DB is ready because of reason - Reason: ReadinessCheckSucceeded + Last Transition Time: 2026-05-14T08:44:22Z + Message: database demo/qdrant-sample is ready + Reason: AllReplicasReady Status: True Type: Ready - Last Transition Time: 2024-10-01T10:02:00Z + Last Transition Time: 2026-05-14T08:44:23Z Message: The Qdrant: demo/qdrant-sample is successfully provisioned. + Observed Generation: 3 Reason: DatabaseSuccessfullyProvisioned Status: True Type: Provisioned - Phase: Ready + Phase: Ready +Events: + ``` ## Find Underlying Kubernetes Resources -KubeDB operator creates a StatefulSet, PVCs, PVs, and Services for the Qdrant database. Let's check them: +KubeDB operator creates a Petset, PVCs, PVs, and Services for the Qdrant database. Let's check them: ```bash -$ kubectl get statefulset -n demo qdrant-sample -NAME READY AGE -qdrant-sample 3/3 15m +$ kubectl get petset -n demo qdrant-sample +NAME AGE +qdrant-sample 2m34s $ kubectl get pvc -n demo -NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE -data-qdrant-sample-0 Bound pvc-ccbba9d2-5556-49cd-9ce8-23c28ad56f12 1Gi RWO standard 15m -data-qdrant-sample-1 Bound pvc-ccbba9d2-5556-49cd-9ce8-23c28ad56f13 1Gi RWO standard 15m -data-qdrant-sample-2 Bound pvc-ccbba9d2-5556-49cd-9ce8-23c28ad56f14 1Gi RWO standard 15m +NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS VOLUMEATTRIBUTESCLASS AGE +data-qdrant-sample-0 Bound pvc-0015c0ad-4ddd-404c-9d8b-b9ea1f6cc15f 1Gi RWO standard $ kubectl get pv -n demo -NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE -pvc-ccbba9d2-5556-49cd-9ce8-23c28ad56f12 1Gi RWO Delete Bound demo/data-qdrant-sample-0 standard 15m -pvc-ccbba9d2-5556-49cd-9ce8-23c28ad56f13 1Gi RWO Delete Bound demo/data-qdrant-sample-1 standard 15m -pvc-ccbba9d2-5556-49cd-9ce8-23c28ad56f14 1Gi RWO Delete Bound demo/data-qdrant-sample-2 standard 15m +NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS VOLUMEATTRIBUTESCLASS REASON AGE +pvc-0015c0ad-4ddd-404c-9d8b-b9ea1f6cc15f 1Gi RWO Delete Bound demo/data-qdrant-sample-0 standard 4m14s + $ kubectl get service -n demo -NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE -qdrant-sample ClusterIP 10.96.128.61 6333/TCP 15m -qdrant-sample-pods ClusterIP None 6333/TCP 15m +NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE +qdrant-sample ClusterIP 10.43.18.112 6333/TCP,6334/TCP 5m36s +qdrant-sample-pods ClusterIP None 6335/TCP 5m36s + ``` ## Verify Qdrant YAML Output @@ -205,84 +249,97 @@ $ kubectl get qdrant -n demo qdrant-sample -o yaml apiVersion: kubedb.com/v1alpha2 kind: Qdrant metadata: - creationTimestamp: "2025-06-01T10:00:00Z" + annotations: + kubectl.kubernetes.io/last-applied-configuration: | + {"apiVersion":"kubedb.com/v1alpha2","kind":"Qdrant","metadata":{"annotations":{},"name":"qdrant-sample","namespace":"demo"},"spec":{"deletionPolicy":"WipeOut","mode":"Standalone","storage":{"accessModes":["ReadWriteOnce"],"resources":{"requests":{"storage":"1Gi"}},"storageClassName":"standard"},"version":"1.17.0"}} + creationTimestamp: "2026-05-14T08:43:57Z" finalizers: - - kubedb.com - generation: 2 + - kubedb.com + generation: 3 name: qdrant-sample namespace: demo - resourceVersion: "225923" - uid: e5c9292b-f3a3-4dbf-95c8-1b544096e1d4 + resourceVersion: "3259342" + uid: 42df526b-fba5-4d21-aea4-d20ae5f36f30 spec: authSecret: + activeFrom: "2026-05-14T08:43:57Z" + apiGroup: "" kind: Secret name: qdrant-sample-auth - deletionPolicy: DoNotTerminate + deletionPolicy: WipeOut healthChecker: - failureThreshold: 1 + failureThreshold: 3 periodSeconds: 10 timeoutSeconds: 10 + mode: Standalone podTemplate: controller: {} + metadata: {} spec: containers: - - name: qdrant - resources: - limits: - memory: 2Gi - requests: - cpu: 500m - memory: 2Gi - initContainers: - - name: qdrant-init - resources: - limits: - memory: 512Mi - requests: - cpu: 200m - memory: 512Mi - replicas: 3 + - name: qdrant + resources: + limits: + memory: 1Gi + requests: + cpu: 500m + memory: 1Gi + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + runAsGroup: 1000 + runAsNonRoot: true + runAsUser: 1000 + seccompProfile: + type: RuntimeDefault + podPlacementPolicy: + name: default + securityContext: + fsGroup: 1000 + serviceAccountName: qdrant-sample + replicas: 1 storage: accessModes: - - ReadWriteOnce + - ReadWriteOnce resources: requests: storage: 1Gi storageClassName: standard storageType: Durable - version: "1.17.0" + version: 1.17.0 status: conditions: - - lastTransitionTime: "2025-06-01T10:00:00Z" - message: 'The KubeDB operator has started the provisioning of Qdrant: demo/qdrant-sample' - observedGeneration: 2 - reason: DatabaseProvisioningStartedSuccessfully - status: "True" - type: ProvisioningStarted - - lastTransitionTime: "2025-06-01T10:01:30Z" - message: All replicas are ready for Qdrant demo/qdrant-sample - observedGeneration: 2 - reason: AllReplicasReady - status: "True" - type: ReplicaReady - - lastTransitionTime: "2025-06-01T10:02:00Z" - message: database demo/qdrant-sample is accepting connection - observedGeneration: 2 - reason: AcceptingConnection - status: "True" - type: AcceptingConnection - - lastTransitionTime: "2025-06-01T10:02:00Z" - message: database demo/qdrant-sample is ready - observedGeneration: 2 - reason: AllReplicasReady - status: "True" - type: Ready - - lastTransitionTime: "2025-06-01T10:02:00Z" - message: 'The Qdrant: demo/qdrant-sample is successfully provisioned.' - observedGeneration: 2 - reason: DatabaseSuccessfullyProvisioned - status: "True" - type: Provisioned + - lastTransitionTime: "2026-05-14T08:43:57Z" + message: 'The KubeDB operator has started the provisioning of Qdrant: demo qdrant-sample' + observedGeneration: 1 + reason: DatabaseProvisioningStartedSuccessfully + status: "True" + type: ProvisioningStarted + - lastTransitionTime: "2026-05-14T08:44:11Z" + message: All desired replicas are ready. + observedGeneration: 3 + reason: AllReplicasReady + status: "True" + type: ReplicaReady + - lastTransitionTime: "2026-05-14T08:44:22Z" + message: database demo/qdrant-sample is accepting connection + observedGeneration: 3 + reason: AcceptingConnection + status: "True" + type: AcceptingConnection + - lastTransitionTime: "2026-05-14T08:44:22Z" + message: database demo/qdrant-sample is ready + reason: AllReplicasReady + status: "True" + type: Ready + - lastTransitionTime: "2026-05-14T08:44:23Z" + message: 'The Qdrant: demo/qdrant-sample is successfully provisioned.' + observedGeneration: 3 + reason: DatabaseSuccessfullyProvisioned + status: "True" + type: Provisioned phase: Ready ``` @@ -292,24 +349,44 @@ KubeDB creates a Secret containing authentication credentials for the Qdrant clu ```bash $ kubectl get secret -n demo qdrant-sample-auth -o yaml +``` +```yaml apiVersion: v1 data: - api-key: + api-key: ZUlxZVlMcnB4dmJ4SFBsbA== + read-only-api-key: M04yN25yakF3WWtlS0hPYg== kind: Secret metadata: + annotations: + kubedb.com/auth-active-from: "2026-05-14T08:43:57Z" + creationTimestamp: "2026-05-14T08:43:57Z" + labels: + app.kubernetes.io/component: database + app.kubernetes.io/instance: qdrant-sample + app.kubernetes.io/managed-by: kubedb.com + app.kubernetes.io/name: qdrants.kubedb.com name: qdrant-sample-auth namespace: demo + ownerReferences: + - apiVersion: kubedb.com/v1alpha2 + blockOwnerDeletion: true + controller: true + kind: Qdrant + name: qdrant-sample + uid: 42df526b-fba5-4d21-aea4-d20ae5f36f30 + resourceVersion: "3259248" + uid: 760da5c0-76de-48ae-838d-01a63cf90b8b type: Opaque ``` Now, let's connect to the Qdrant cluster using port forwarding: ```bash -$ kubectl port-forward -n demo svc/qdrant-sample 6333:6333 & +$ kubectl port-forward -n demo svc/qdrant-sample 6333:6333 $ export QDRANT_API_KEY=$(kubectl get secret -n demo qdrant-sample-auth -o jsonpath='{.data.api-key}' | base64 -d) $ curl -H "api-key: $QDRANT_API_KEY" http://localhost:6333/collections -{"result":{"collections":[]},"status":"ok","time":0.001} +{"result":{"collections":[{"name":"KubeDBHealthCheckCollection"}]},"status":"ok","time":0.00001235} ``` ## AppBinding @@ -326,8 +403,11 @@ items: - apiVersion: appcatalog.appscode.com/v1alpha1 kind: AppBinding metadata: - creationTimestamp: "2025-06-01T10:00:30Z" - generation: 1 + annotations: + kubectl.kubernetes.io/last-applied-configuration: | + {"apiVersion":"kubedb.com/v1alpha2","kind":"Qdrant","metadata":{"annotations":{},"name":"qdrant-sample","namespace":"demo"},"spec":{"deletionPolicy":"WipeOut","mode":"Standalone","storage":{"accessModes":["ReadWriteOnce"],"resources":{"requests":{"storage":"1Gi"}},"storageClassName":"standard"},"version":"1.17.0"}} + creationTimestamp: "2026-05-14T08:44:00Z" + generation: 2 labels: app.kubernetes.io/component: database app.kubernetes.io/instance: qdrant-sample @@ -341,9 +421,9 @@ items: controller: true kind: Qdrant name: qdrant-sample - uid: e5c9292b-f3a3-4dbf-95c8-1b544096e1d4 - resourceVersion: "225711" - uid: 4d111a65-cf3d-4a74-a77e-24f2dee690df + uid: 42df526b-fba5-4d21-aea4-d20ae5f36f30 + resourceVersion: "3259280" + uid: 7183b027-3eda-42ed-95f7-0a366d493464 spec: appRef: apiGroup: kubedb.com @@ -353,13 +433,14 @@ items: clientConfig: service: name: qdrant-sample - path: / port: 6333 scheme: http secret: + apiGroup: "" + kind: Secret name: qdrant-sample-auth type: kubedb.com/qdrant - version: "1.17" + version: 1.17.0 kind: List metadata: resourceVersion: "" @@ -401,13 +482,13 @@ Now, check that the PVCs and Secrets still exist: ```bash $ kubectl get secret,pvc -n demo -NAME TYPE DATA AGE -secret/qdrant-sample-auth Opaque 2 30m +NAME TYPE DATA AGE +secret/qdrant-sample-auth Opaque 2 11m +secret/qdrant-sample-f36f30 Opaque 1 11m + +NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS VOLUMEATTRIBUTESCLASS AGE +persistentvolumeclaim/data-qdrant-sample-0 Bound pvc-0015c0ad-4ddd-404c-9d8b-b9ea1f6cc15f 1Gi RWO standard 11m -NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE -persistentvolumeclaim/data-qdrant-sample-0 Bound pvc-ccbba9d2-5556-49cd-9ce8-23c28ad56f12 1Gi RWO standard 29m -persistentvolumeclaim/data-qdrant-sample-1 Bound pvc-ccbba9d2-5556-49cd-9ce8-23c28ad56f13 1Gi RWO standard 29m -persistentvolumeclaim/data-qdrant-sample-2 Bound pvc-ccbba9d2-5556-49cd-9ce8-23c28ad56f14 1Gi RWO standard 29m ``` You can recreate your Qdrant database later using these PVCs and Secrets. diff --git a/docs/guides/qdrant/reconfigure-tls/overview.md b/docs/guides/qdrant/reconfigure-tls/overview.md index 620d2fabe..b6d12a37a 100644 --- a/docs/guides/qdrant/reconfigure-tls/overview.md +++ b/docs/guides/qdrant/reconfigure-tls/overview.md @@ -42,7 +42,7 @@ The Reconfigure TLS process consists of the following steps: 2. `KubeDB-Provisioner` operator watches the `Qdrant` CR. -3. When the operator finds a `Qdrant` CR, it creates a `StatefulSet` and related necessary stuff like secrets, services, etc. +3. When the operator finds a `Qdrant` CR, it creates a `Petset` and related necessary stuff like secrets, services, etc. 4. Then, in order to reconfigure TLS of the `Qdrant` database, the user creates a `QdrantOpsRequest` CR specifying the desired TLS configuration. The user can add TLS to an existing non-TLS database, rotate the existing certificates, or remove TLS entirely. diff --git a/docs/guides/qdrant/reconfigure/overview.md b/docs/guides/qdrant/reconfigure/overview.md index 5b0261ad8..c869d99c7 100644 --- a/docs/guides/qdrant/reconfigure/overview.md +++ b/docs/guides/qdrant/reconfigure/overview.md @@ -14,7 +14,7 @@ section_menu_id: guides # Reconfiguring Qdrant -This guide will give an overview of how KubeDB Ops-manager reconfigures a `Qdrant` database. +This guide will give an overview on how KubeDB Ops-manager operator reconfigures `Qdrant` database. ## Before You Begin @@ -22,26 +22,33 @@ This guide will give an overview of how KubeDB Ops-manager reconfigures a `Qdran - [Qdrant](/docs/guides/qdrant/concepts/qdrant.md) - [QdrantOpsRequest](/docs/guides/qdrant/concepts/opsrequest.md) -## How Reconfiguration Works +## How Reconfiguring Qdrant Process Works -The Reconfiguration process consists of the following steps: +The following diagram shows how KubeDB Ops-manager operator reconfigures `Qdrant` database. Open the image in a new tab to see the enlarged version. -1. At first, a user creates a `Qdrant` CR. +

+  Reconfiguring process of Qdrant +
Fig: Reconfiguring process of Qdrant
+
-2. `KubeDB-Provisioner` operator watches the `Qdrant` CR. +The Reconfiguring Qdrant process consists of the following steps: -3. When the operator finds a `Qdrant` CR, it creates a `StatefulSet` and related necessary stuff like secrets, services, etc. +1. At first, a user creates a `Qdrant` Custom Resource (CR). -4. Then, in order to reconfigure the `Qdrant` database, the user creates a `QdrantOpsRequest` CR with the new configuration. The user can provide the new configuration either via a new config secret, via `applyConfig`, or by removing the custom configuration (reverting to defaults). +2. `KubeDB` Provisioner operator watches the `Qdrant` CR. + +3. When the operator finds a `Qdrant` CR, it creates required number of `PetSets` and related necessary stuff like secrets, services, etc. + +4. Then, in order to reconfigure the `Qdrant` database the user creates a `QdrantOpsRequest` CR with desired information. 5. `KubeDB` Ops-manager operator watches the `QdrantOpsRequest` CR. -6. When it finds a `QdrantOpsRequest` CR, it pauses the `Qdrant` object so that the `KubeDB-Provisioner` operator doesn't perform any operations on the `Qdrant` during the reconfiguration process. +6. When it finds a `QdrantOpsRequest` CR, it halts the `Qdrant` object which is referred from the `QdrantOpsRequest`. So, the `KubeDB` Provisioner operator doesn't perform any operations on the `Qdrant` object during the reconfiguring process. -7. Then the `KubeDB` Ops-manager operator updates the configuration secret and restarts the pods in a rolling fashion to apply the new configuration. +7. Then the `KubeDB` Ops-manager operator will replace the existing configuration with the new configuration provided or merge the new configuration with the existing configuration according to the `QdrantOpsRequest` CR. -8. After the successful configuration update, the `KubeDB` Ops-manager updates the `Qdrant` object to reflect the updated configuration state. +8. Then the `KubeDB` Ops-manager operator will restart the related PetSet Pods so that they restart with the new configuration defined in the `QdrantOpsRequest` CR. -9. After the successful reconfiguration, the `KubeDB` Ops-manager resumes the `Qdrant` object so that the `KubeDB-Provisioner` resumes its usual operations. +9. After the successful reconfiguring of the `Qdrant`, the `KubeDB` Ops-manager operator resumes the `Qdrant` object so that the `KubeDB` Provisioner operator resumes its usual operations. -In the next doc, we are going to show a step-by-step guide on reconfiguring a Qdrant database using `QdrantOpsRequest` CRD. +In the next docs, we are going to show a step-by-step guide on reconfiguring Qdrant database using `QdrantOpsRequest` CR. diff --git a/docs/guides/qdrant/reconfigure/reconfigure.md b/docs/guides/qdrant/reconfigure/reconfigure.md index c8362dcca..d4937b24b 100644 --- a/docs/guides/qdrant/reconfigure/reconfigure.md +++ b/docs/guides/qdrant/reconfigure/reconfigure.md @@ -34,27 +34,35 @@ $ kubectl create ns demo namespace/demo created ``` -> **Note:** YAML files used in this tutorial are stored in [docs/guides/qdrant/reconfigure/yamls](/docs/guides/qdrant/reconfigure/yamls) directory of [kubedb/docs](https://github.com/kubedb/docs) repository. +> **Note:** YAML files used in this tutorial are stored in [docs/examples/qdrant/reconfigure](/docs/examples/qdrant/reconfigure) directory of [kubedb/docs](https://github.com/kubedb/docs) repository. -### Prepare Qdrant +## Prepare Qdrant Now, we are going to deploy a `Qdrant` cluster with an initial configuration. -#### Deploy Qdrant with custom config +### Deploy Qdrant with custom config -First, we will create a `qdrant.yaml` config file containing our initial configuration settings. +Below is the YAML of the configuration `Secret` that we are going to create: ```yaml -# qdrant.yaml -storage: - performance: - max_search_threads: 4 +apiVersion: v1 +stringData: + config.yaml: | + log_level: DEBUG + performance: + max_search_threads: 4 + update_rate_limit: 100 +kind: Secret +metadata: + name: qdrant-configuration + namespace: demo +type: Opaque ``` -Now, we will create a secret with this configuration file. +Let's create the `Secret` we have shown above: ```bash -$ kubectl create secret generic -n demo qdrant-configuration --from-file=./qdrant.yaml +$ kubectl apply -f https://github.com/kubedb/docs/raw/{{< param "info.version" >}}/docs/examples/qdrant/reconfigure/configuration-secret.yaml secret/qdrant-configuration created ``` @@ -83,7 +91,7 @@ spec: Let's create the `Qdrant` CR we have shown above: ```bash -$ kubectl create -f https://github.com/kubedb/docs/raw/{{< param "info.version" >}}/docs/guides/qdrant/reconfigure/yamls/qdrant.yaml +$ kubectl create -f https://github.com/kubedb/docs/raw/{{< param "info.version" >}}/docs/examples/qdrant/reconfigure/qdrant.yaml qdrant.kubedb.com/qdrant-sample created ``` @@ -95,27 +103,35 @@ NAME VERSION STATUS AGE qdrant-sample 1.17.0 Ready 3m42s ``` -### Reconfigure using new config secret +## Reconfigure using new config secret Now we will reconfigure this database to change `max_search_threads` to `8`. -First, we will create a new `qdrant.yaml` file containing the updated configuration: +Below is the YAML of the new configuration `Secret` that we are going to create: ```yaml -# qdrant.yaml -storage: - performance: - max_search_threads: 8 +apiVersion: v1 +stringData: + config.yaml: | + log_level: DEBUG + performance: + max_search_threads: 8 + update_rate_limit: 100 +kind: Secret +metadata: + name: new-qdrant-configuration + namespace: demo +type: Opaque ``` -Then, we will create a new secret with this configuration file: +Let's create the `Secret` we have shown above: ```bash -$ kubectl create secret generic -n demo new-qdrant-configuration --from-file=./qdrant.yaml +$ kubectl apply -f https://github.com/kubedb/docs/raw/{{< param "info.version" >}}/docs/examples/qdrant/reconfigure/new-configuration-secret.yaml secret/new-qdrant-configuration created ``` -#### Create QdrantOpsRequest +### Create QdrantOpsRequest Now, we will use this secret to replace the previous secret using a `QdrantOpsRequest` CR. Below is the YAML of the `QdrantOpsRequest` that we are going to create: @@ -147,11 +163,11 @@ Here, Let's create the `QdrantOpsRequest` CR we have shown above: ```bash -$ kubectl apply -f https://github.com/kubedb/docs/raw/{{< param "info.version" >}}/docs/guides/qdrant/reconfigure/yamls/reconfigure-using-secret.yaml +$ kubectl apply -f https://github.com/kubedb/docs/raw/{{< param "info.version" >}}/docs/examples/qdrant/reconfigure/reconfigure-using-secret.yaml qdrantopsrequest.ops.kubedb.com/qdops-reconfigure-config created ``` -#### Verify the new configuration is working +### Verify the new configuration is working If everything goes well, `KubeDB` Enterprise operator will update the `configSecret` of the `Qdrant` object. @@ -160,10 +176,10 @@ Let's wait for `QdrantOpsRequest` to be `Successful`: ```bash $ kubectl get qdops -n demo NAME TYPE STATUS AGE -qdops-reconfigure-config Reconfigure Successful 3m21s +qdops-reconfigure-config Reconfigure Successful 3m ``` -### Reconfigure using applyConfig +## Reconfigure using applyConfig We can also reconfigure our existing secret by modifying configuration inline using `applyConfig`. Below is the YAML of the `QdrantOpsRequest`: @@ -179,10 +195,11 @@ spec: name: qdrant-sample configuration: applyConfig: - qdrant.yaml: | - storage: - performance: - max_search_threads: 6 + config.yaml: | + log_level: DEBUG + performance: + max_search_threads: 6 + update_rate_limit: 100 ``` > **Note:** You can modify multiple fields of your current configuration using `applyConfig`. If you don't have any existing config secret, `applyConfig` will create a new secret for you. If a config secret already exists, `applyConfig` will merge the new configuration with the existing one. @@ -194,21 +211,21 @@ Here, - `spec.configuration.applyConfig` contains the inline configuration to apply. ```bash -$ kubectl apply -f https://github.com/kubedb/docs/raw/{{< param "info.version" >}}/docs/guides/qdrant/reconfigure/yamls/apply-config.yaml +$ kubectl apply -f https://github.com/kubedb/docs/raw/{{< param "info.version" >}}/docs/examples/qdrant/reconfigure/apply-config.yaml qdrantopsrequest.ops.kubedb.com/qdops-reconfigure-apply-config created ``` -#### Verify the new configuration is working +### Verify the new configuration is working Let's wait for `QdrantOpsRequest` to be `Successful`: ```bash $ kubectl get qdops qdops-reconfigure-apply-config -n demo NAME TYPE STATUS AGE -qdops-reconfigure-apply-config Reconfigure Successful 4m59s +qdops-reconfigure-apply-config Reconfigure Successful 5m30s ``` -### Remove Custom Configuration +## Remove Custom Configuration We can also remove existing custom config using `QdrantOpsRequest`. Set `spec.configuration.removeCustomConfig: true` to remove the existing custom configuration. @@ -227,7 +244,7 @@ spec: ``` ```bash -$ kubectl apply -f https://github.com/kubedb/docs/raw/{{< param "info.version" >}}/docs/guides/qdrant/reconfigure/yamls/remove-config.yaml +$ kubectl apply -f https://github.com/kubedb/docs/raw/{{< param "info.version" >}}/docs/examples/qdrant/reconfigure/remove-config.yaml qdrantopsrequest.ops.kubedb.com/qdops-reconfigure-remove created ``` @@ -236,7 +253,7 @@ Let's wait for `QdrantOpsRequest` to be `Successful`: ```bash $ kubectl get qdops qdops-reconfigure-remove -n demo NAME TYPE STATUS AGE -qdops-reconfigure-remove Reconfigure Successful 2m10s +qdops-reconfigure-remove Reconfigure Successful 97s ``` After this, the `Qdrant` CR will no longer reference a `configSecret` and the database will use its default configuration. @@ -252,7 +269,14 @@ After this, the `Qdrant` CR will no longer reference a `configSecret` and the da To clean up the Kubernetes resources created by this tutorial, run: ```bash -kubectl delete qdrantopsrequest -n demo qdops-reconfigure-config qdops-reconfigure-apply-config qdops-reconfigure-remove -kubectl delete qdrant -n demo qdrant-sample -kubectl delete ns demo +$ kubectl delete qdrantopsrequest -n demo qdops-reconfigure-config qdops-reconfigure-apply-config qdops-reconfigure-remove +qdrantopsrequest.ops.kubedb.com "qdops-reconfigure-config" deleted +qdrantopsrequest.ops.kubedb.com "qdops-reconfigure-apply-config" deleted +qdrantopsrequest.ops.kubedb.com "qdops-reconfigure-remove" deleted + +$ kubectl delete qdrant -n demo qdrant-sample +qdrant.kubedb.com "qdrant-sample" deleted + +$ kubectl delete ns demo +namespace "demo" deleted ``` \ No newline at end of file diff --git a/docs/guides/qdrant/restart/restart.md b/docs/guides/qdrant/restart/restart.md index f5c0288d8..ffbba14bd 100644 --- a/docs/guides/qdrant/restart/restart.md +++ b/docs/guides/qdrant/restart/restart.md @@ -95,7 +95,7 @@ spec: Let's create the `QdrantOpsRequest` CR we have shown above, ```bash -$ kubectl create -f https://github.com/kubedb/docs/raw/{{< param "info.version" >}}/docs/examples/qdrant/restart/ops.yaml +$ kubectl create -f https://github.com/kubedb/docs/raw/{{< param "info.version" >}}/docs/examples/qdrant/restart/ops-request.yaml qdrantopsrequest.ops.kubedb.com/qdops-restart created ``` @@ -104,7 +104,7 @@ Now the Ops-manager operator will restart the Qdrant pods one by one, waiting fo ```bash $ kubectl get qdops -n demo qdops-restart NAME TYPE STATUS AGE -qdops-restart Restart Successful 3m25s +qdops-restart Restart Successful 66s ``` ```bash @@ -112,40 +112,83 @@ $ kubectl get qdops -n demo qdops-restart -o yaml apiVersion: ops.kubedb.com/v1alpha1 kind: QdrantOpsRequest metadata: + annotations: + kubectl.kubernetes.io/last-applied-configuration: | + {"apiVersion":"ops.kubedb.com/v1alpha1","kind":"QdrantOpsRequest","metadata":{"annotations":{},"name":"qdops-restart","namespace":"demo"},"spec":{"apply":"Always","databaseRef":{"name":"qdrant-sample"},"timeout":"3m","type":"Restart"}} + creationTimestamp: "2026-05-15T05:43:05Z" + generation: 1 name: qdops-restart namespace: demo + resourceVersion: "3357120" + uid: f90d628f-1db2-4fbc-a20d-e449ab7215a8 spec: apply: Always databaseRef: name: qdrant-sample + maxRetries: 1 timeout: 3m type: Restart status: conditions: - - lastTransitionTime: "2026-05-01T10:00:00Z" - message: Qdrant ops request is restarting nodes + - lastTransitionTime: "2026-05-15T05:43:05Z" + message: Qdrant ops-request has started to restart Qdrant nodes observedGeneration: 1 reason: Restart status: "True" type: Restart - - lastTransitionTime: "2026-05-01T10:02:30Z" - message: Successfully restarted all nodes + - lastTransitionTime: "2026-05-15T05:44:07Z" + message: Successfully Restarted Qdrant nodes observedGeneration: 1 reason: RestartNodes status: "True" type: RestartNodes - - lastTransitionTime: "2026-05-01T10:00:10Z" - message: evict pod; ConditionStatus:True + - lastTransitionTime: "2026-05-15T05:43:17Z" + message: get pod; ConditionStatus:True; PodName:qdrant-sample-0 observedGeneration: 1 status: "True" - type: EvictPod - - lastTransitionTime: "2026-05-01T10:01:05Z" - message: check pod ready; ConditionStatus:True + type: GetPod--qdrant-sample-0 + - lastTransitionTime: "2026-05-15T05:43:18Z" + message: evict pod; ConditionStatus:True; PodName:qdrant-sample-0 observedGeneration: 1 status: "True" - type: CheckPodReady - - lastTransitionTime: "2026-05-01T10:02:30Z" - message: Successfully completed the modification process. + type: EvictPod--qdrant-sample-0 + - lastTransitionTime: "2026-05-15T05:43:32Z" + message: running pod; ConditionStatus:True; PodName:qdrant-sample-0 + observedGeneration: 1 + status: "True" + type: RunningPod--qdrant-sample-0 + - lastTransitionTime: "2026-05-15T05:43:37Z" + message: get pod; ConditionStatus:True; PodName:qdrant-sample-1 + observedGeneration: 1 + status: "True" + type: GetPod--qdrant-sample-1 + - lastTransitionTime: "2026-05-15T05:43:38Z" + message: evict pod; ConditionStatus:True; PodName:qdrant-sample-1 + observedGeneration: 1 + status: "True" + type: EvictPod--qdrant-sample-1 + - lastTransitionTime: "2026-05-15T05:43:42Z" + message: running pod; ConditionStatus:True; PodName:qdrant-sample-1 + observedGeneration: 1 + status: "True" + type: RunningPod--qdrant-sample-1 + - lastTransitionTime: "2026-05-15T05:43:47Z" + message: get pod; ConditionStatus:True; PodName:qdrant-sample-2 + observedGeneration: 1 + status: "True" + type: GetPod--qdrant-sample-2 + - lastTransitionTime: "2026-05-15T05:43:48Z" + message: evict pod; ConditionStatus:True; PodName:qdrant-sample-2 + observedGeneration: 1 + status: "True" + type: EvictPod--qdrant-sample-2 + - lastTransitionTime: "2026-05-15T05:44:02Z" + message: running pod; ConditionStatus:True; PodName:qdrant-sample-2 + observedGeneration: 1 + status: "True" + type: RunningPod--qdrant-sample-2 + - lastTransitionTime: "2026-05-15T05:44:08Z" + message: Controller has successfully restarted the Qdrant replicas observedGeneration: 1 reason: Successful status: "True" @@ -165,7 +208,12 @@ status: To cleanup the Kubernetes resources created by this tutorial, run: ```bash -kubectl delete qdrantopsrequest -n demo qdops-restart -kubectl delete qdrant -n demo qdrant-sample -kubectl delete ns demo +$ kubectl delete qdrantopsrequest -n demo qdops-restart +qdrantopsrequest.ops.kubedb.com "qdops-restart" deleted + +$ kubectl delete qdrant -n demo qdrant-sample +qdrant.kubedb.com "qdrant-sample" deleted + +$ kubectl delete ns demo +namespace "demo" deleted ``` diff --git a/docs/guides/qdrant/rotate-auth/overview.md b/docs/guides/qdrant/rotate-auth/overview.md index ec0c467ba..5e42ce89e 100644 --- a/docs/guides/qdrant/rotate-auth/overview.md +++ b/docs/guides/qdrant/rotate-auth/overview.md @@ -1,5 +1,5 @@ --- -title: Rotating Qdrant Credentials +title: Rotate Authentication Overview menu: docs_{{ .version }}: identifier: qdrant-rotate-auth-overview @@ -12,9 +12,9 @@ section_menu_id: guides > New to KubeDB? Please start [here](/docs/README.md). -# Rotating Qdrant Authentication Credentials +# Rotate Authentication of Qdrant -This guide will give an overview of how KubeDB Ops-manager rotates the authentication credentials of a `Qdrant` database. +This guide will give an overview on how KubeDB Ops-manager operator Rotate Authentication configuration. ## Before You Begin @@ -22,26 +22,26 @@ This guide will give an overview of how KubeDB Ops-manager rotates the authentic - [Qdrant](/docs/guides/qdrant/concepts/qdrant.md) - [QdrantOpsRequest](/docs/guides/qdrant/concepts/opsrequest.md) -## How Rotate Auth Works +## How Rotate Qdrant Authentication Configuration Process Works -The Rotate Auth process consists of the following steps: +The authentication rotation process for Qdrant using KubeDB involves the following steps: -1. At first, a user creates a `Qdrant` CR. +1. A user first creates a `Qdrant` Custom Resource Object (CRO). -2. `KubeDB-Provisioner` operator watches the `Qdrant` CR. +2. The `KubeDB Provisioner operator` continuously watches for `Qdrant` CROs. -3. When the operator finds a `Qdrant` CR, it creates a `StatefulSet` and generates an `authSecret` containing the initial API key for the Qdrant database. +3. When the operator detects a `Qdrant` CR, it provisions the required `PetSets`, along with related resources such as secrets, services, and other dependencies. -4. Then, in order to rotate the authentication credentials, the user creates a `QdrantOpsRequest` CR with `type: RotateAuth`. The user can optionally provide a new custom secret, or let KubeDB auto-generate a new API key. +4. To initiate authentication rotation, the user creates a `QdrantOpsRequest` CR with the desired configuration. -5. `KubeDB` Ops-manager operator watches the `QdrantOpsRequest` CR. +5. The `KubeDB Ops-manager` operator watches for `QdrantOpsRequest` CRs. -6. When it finds a `QdrantOpsRequest` CR, it pauses the `Qdrant` object so that the `KubeDB-Provisioner` operator doesn't perform any operations on the `Qdrant` during the credential rotation process. +6. Upon detecting a `QdrantOpsRequest`, the operator pauses the referenced `Qdrant` object, ensuring that the Provisioner operator does not perform any operations during the authentication rotation process. -7. Then the `KubeDB` Ops-manager operator generates a new API key (or uses the provided secret), updates the `authSecret`, and restarts the pods in a rolling fashion to apply the new credentials. +7. The `Ops-manager` operator then updates the necessary configuration (such as credentials) based on the provided `QdrantOpsRequest` specification. -8. After the successful credential rotation, the `KubeDB` Ops-manager updates the `Qdrant` object to reflect the updated auth state. +8. After applying the updated configuration, the operator restarts all `Qdrant` Pods so they come up with the new authentication environment variables and settings. -9. After the successful Rotate Auth, the `KubeDB` Ops-manager resumes the `Qdrant` object so that the `KubeDB-Provisioner` resumes its usual operations. +9. Once the authentication rotation is completed successfully, the operator resumes the `Qdrant` object, allowing the Provisioner operator to continue its usual operations. -In the next doc, we are going to show a step-by-step guide on rotating authentication credentials of a Qdrant database using `QdrantOpsRequest` CRD. +In the next section, we will walk you through a step-by-step guide to rotating Qdrant authentication using the `QdrantOpsRequest` CRD. diff --git a/docs/guides/qdrant/rotate-auth/rotate-auth.md b/docs/guides/qdrant/rotate-auth/rotate-auth.md index 2105cf8df..938efe152 100644 --- a/docs/guides/qdrant/rotate-auth/rotate-auth.md +++ b/docs/guides/qdrant/rotate-auth/rotate-auth.md @@ -111,7 +111,7 @@ Here, Let's create the `QdrantOpsRequest` CR we have shown above: ```bash -$ kubectl apply -f https://github.com/kubedb/docs/raw/{{< param "info.version" >}}/docs/examples/qdrant/rotate-auth/ops.yaml +$ kubectl apply -f https://github.com/kubedb/docs/raw/{{< param "info.version" >}}/docs/examples/qdrant/rotate-auth/ops-request.yaml qdrantopsrequest.ops.kubedb.com/qdops-rotate-auth created ``` @@ -142,6 +142,26 @@ You can see that the API key has been rotated. The new key is different from the You can also rotate the authentication credentials using a custom secret that you provide: +```yaml +apiVersion: v1 +stringData: + api-key: MyCus0mAPIKey +kind: Secret +metadata: + name: my-custom-auth-secret + namespace: demo +type: Opaque +``` + +Let's create the `Secret` we have shown above: + +```bash +$ kubectl apply -f https://github.com/kubedb/docs/raw/{{< param "info.version" >}}/docs/examples/qdrant/rotate-auth/custom-auth-secret.yaml +secret/my-custom-auth-secret created +``` + +Now, create a `QdrantOpsRequest` with the custom secret reference: + ```yaml apiVersion: ops.kubedb.com/v1alpha1 kind: QdrantOpsRequest @@ -173,8 +193,16 @@ qdrantopsrequest.ops.kubedb.com/qdops-rotate-auth-custom created To clean up the Kubernetes resources created by this tutorial, run: ```bash -kubectl delete qdrantopsrequest -n demo qdops-rotate-auth qdops-rotate-auth-custom -kubectl delete secret -n demo my-custom-auth-secret -kubectl delete qdrant -n demo qdrant-sample -kubectl delete ns demo +$ kubectl delete qdrantopsrequest -n demo qdops-rotate-auth qdops-rotate-auth-custom +qdrantopsrequest.ops.kubedb.com "qdops-rotate-auth" deleted +qdrantopsrequest.ops.kubedb.com "qdops-rotate-auth-custom" deleted + +$ kubectl delete secret -n demo my-custom-auth-secret +secret "my-custom-auth-secret" deleted + +$ kubectl delete qdrant -n demo qdrant-sample +qdrant.kubedb.com "qdrant-sample" deleted + +$ kubectl delete ns demo +namespace "demo" deleted ``` \ No newline at end of file diff --git a/docs/guides/qdrant/scaling/horizontal-scaling/horizontal-scaling.md b/docs/guides/qdrant/scaling/horizontal-scaling/horizontal-scaling.md index 69e7b4cfd..8d3bb2ff7 100644 --- a/docs/guides/qdrant/scaling/horizontal-scaling/horizontal-scaling.md +++ b/docs/guides/qdrant/scaling/horizontal-scaling/horizontal-scaling.md @@ -34,13 +34,13 @@ $ kubectl create ns demo namespace/demo created ``` -> **Note:** YAML files used in this tutorial are stored in [docs/guides/qdrant/scaling/horizontal-scaling/scale-horizontally/yamls](/docs/guides/qdrant/scaling/horizontal-scaling/scale-horizontally/yamls) directory of [kubedb/docs](https://github.com/kubedb/docs) repository. +> **Note:** YAML files used in this tutorial are stored in [docs/examples/qdrant/scaling/horizontal-scaling](/docs/examples/qdrant/scaling/horizontal-scaling) directory of [kubedb/docs](https://github.com/kubedb/docs) repository. -### Apply Horizontal Scaling on Qdrant Cluster +## Apply Horizontal Scaling on Qdrant Cluster Here, we are going to deploy a `Qdrant` cluster using a supported version by `KubeDB` operator. Then we are going to apply horizontal scaling on it. -#### Prepare Cluster +### Prepare Cluster At first, we are going to deploy a cluster with 3 nodes. Then, we are going to add two additional nodes through horizontal scaling. Finally, we will remove 1 node from the cluster again via horizontal scaling. @@ -70,7 +70,7 @@ spec: Let's create the `Qdrant` CR we have shown above: ```bash -$ kubectl create -f https://github.com/kubedb/docs/raw/{{< param "info.version" >}}/docs/guides/qdrant/scaling/horizontal-scaling/scale-horizontally/yamls/qdrant.yaml +$ kubectl create -f https://github.com/kubedb/docs/raw/{{< param "info.version" >}}/docs/examples/qdrant/scaling/horizontal-scaling/qdrant.yaml qdrant.kubedb.com/qdrant-sample created ``` @@ -113,7 +113,7 @@ $ kubectl get qdrant -n demo qdrant-sample -o=jsonpath='{.spec.replicas}{"\n"}' We are ready to apply the `QdrantOpsRequest` CR to scale horizontally. -#### Scale Up +### Scale Up Here, we are going to scale up the cluster from 3 nodes to 5 nodes. @@ -146,7 +146,7 @@ Here, Let's create the `QdrantOpsRequest` CR we have shown above: ```bash -$ kubectl apply -f https://github.com/kubedb/docs/raw/{{< param "info.version" >}}/docs/guides/qdrant/scaling/horizontal-scaling/scale-horizontally/yamls/hscale-up.yaml +$ kubectl apply -f https://github.com/kubedb/docs/raw/{{< param "info.version" >}}/docs/examples/qdrant/scaling/horizontal-scaling/hscale-up.yaml qdrantopsrequest.ops.kubedb.com/qdops-hscale-up created ``` @@ -175,7 +175,7 @@ qdrant-sample-3 1/1 Running 0 2m qdrant-sample-4 1/1 Running 0 1m ``` -#### Scale Down +### Scale Down Here, we are going to scale down the cluster from 5 nodes to 4 nodes. @@ -198,7 +198,7 @@ spec: Let's create the `QdrantOpsRequest` CR we have shown above: ```bash -$ kubectl apply -f https://github.com/kubedb/docs/raw/{{< param "info.version" >}}/docs/guides/qdrant/scaling/horizontal-scaling/scale-horizontally/yamls/hscale-down.yaml +$ kubectl apply -f https://github.com/kubedb/docs/raw/{{< param "info.version" >}}/docs/examples/qdrant/scaling/horizontal-scaling/hscale-down.yaml qdrantopsrequest.ops.kubedb.com/qdops-hscale-down created ``` diff --git a/docs/guides/qdrant/scaling/horizontal-scaling/overview.md b/docs/guides/qdrant/scaling/horizontal-scaling/overview.md index 1429b7f32..7bf73cd4d 100644 --- a/docs/guides/qdrant/scaling/horizontal-scaling/overview.md +++ b/docs/guides/qdrant/scaling/horizontal-scaling/overview.md @@ -12,9 +12,9 @@ section_menu_id: guides > New to KubeDB? Please start [here](/docs/README.md). -# Qdrant Horizontal Scaling +# Horizontal Scaling Overview -This guide will give an overview of how KubeDB Ops-manager scales the number of nodes in a `Qdrant` database cluster. +This guide will give you an overview of how `KubeDB` Ops Manager scales up/down the number of members of a `Qdrant`. ## Before You Begin @@ -22,26 +22,35 @@ This guide will give an overview of how KubeDB Ops-manager scales the number of - [Qdrant](/docs/guides/qdrant/concepts/qdrant.md) - [QdrantOpsRequest](/docs/guides/qdrant/concepts/opsrequest.md) -## How Horizontal Scaling Works +## How Horizontal Scaling Process Works -The Horizontal Scaling process consists of the following steps: +The following diagram shows how `KubeDB` Ops Manager used to scale up the number of members of a `Qdrant` cluster. Open the image in a new tab to see the enlarged version. + +
+  Horizontal scaling Flow +
Fig: Horizontal scaling process of Qdrant
+
+ +The horizontal scaling process consists of the following steps: 1. At first, a user creates a `Qdrant` CR. -2. `KubeDB-Provisioner` operator watches the `Qdrant` CR. +2. `KubeDB` provisioner operator watches for the `Qdrant` CR. + +3. When it finds one, it creates a `PetSet` and related necessary stuff like secret, service, etc. -3. When the operator finds a `Qdrant` CR, it creates a `StatefulSet` with the specified number of node replicas, along with related necessary stuff like secrets, services, etc. +4. Then, in order to scale the cluster up or down, the user creates a `QdrantOpsRequest` CR with the desired number of replicas after scaling. -4. Then, in order to scale the number of nodes in the `Qdrant` cluster, the user creates a `QdrantOpsRequest` CR with the desired node count. +5. `KubeDB` Ops Manager watches for `QdrantOpsRequest`. -5. `KubeDB` Ops-manager operator watches the `QdrantOpsRequest` CR. +6. When it finds one, it halts the `Qdrant` object so that the `KubeDB` provisioner operator doesn't perform any operation on the `Qdrant` during the scaling process. -6. When it finds a `QdrantOpsRequest` CR, it pauses the `Qdrant` object so that the `KubeDB-Provisioner` operator doesn't perform any operations on the `Qdrant` during the scaling process. +7. Then `KubeDB` Ops Manager will add nodes in case of scale up or remove nodes in case of scale down. -7. Then the `KubeDB` Ops-manager operator scales the `StatefulSet` to the desired number of replicas. +8. Then the `KubeDB` Ops Manager will scale the PetSet replicas to reach the expected number of replicas for the cluster. -8. After the successful scaling of the `StatefulSet`, the `KubeDB` Ops-manager updates the replica count in the `Qdrant` object to reflect the updated state. +9. After successful scaling of the PetSet's replica, the `KubeDB` Ops Manager updates the `spec.replicas` field of `Qdrant` object to reflect the updated cluster state. -9. After the successful Horizontal Scaling, the `KubeDB` Ops-manager resumes the `Qdrant` object so that the `KubeDB-Provisioner` resumes its usual operations. +10. After successful scaling of the `Qdrant` replicas, the `KubeDB` Ops Manager resumes the `Qdrant` object so that the `KubeDB` provisioner operator can resume its usual operations. -In the next doc, we are going to show a step-by-step guide on Horizontal Scaling of a Qdrant database using `QdrantOpsRequest` CRD. +In the next doc, we are going to show a step-by-step guide on scaling of a Qdrant cluster using Horizontal Scaling. diff --git a/docs/guides/qdrant/scaling/vertical-scaling/overview.md b/docs/guides/qdrant/scaling/vertical-scaling/overview.md index b3fea6747..b7604db81 100644 --- a/docs/guides/qdrant/scaling/vertical-scaling/overview.md +++ b/docs/guides/qdrant/scaling/vertical-scaling/overview.md @@ -12,9 +12,9 @@ section_menu_id: guides > New to KubeDB? Please start [here](/docs/README.md). -# Qdrant Vertical Scaling +# Vertical Scaling Qdrant -This guide will give an overview of how KubeDB Ops-manager updates the CPU and memory resources of `Qdrant` database nodes. +This guide will give you an overview of how KubeDB Ops Manager updates the resources(for example Memory, CPU etc.) of the `Qdrant`. ## Before You Begin @@ -22,26 +22,33 @@ This guide will give an overview of how KubeDB Ops-manager updates the CPU and m - [Qdrant](/docs/guides/qdrant/concepts/qdrant.md) - [QdrantOpsRequest](/docs/guides/qdrant/concepts/opsrequest.md) -## How Vertical Scaling Works +## How Vertical Scaling Process Works -The Vertical Scaling process consists of the following steps: +The following diagram shows how the `KubeDB` Ops Manager used to update the resources of the `Qdrant`. Open the image in a new tab to see the enlarged version. + +
+  Vertical scaling process of Qdrant +
Fig: Vertical scaling process of Qdrant
+
+ +The vertical scaling process consists of the following steps: 1. At first, a user creates a `Qdrant` CR. -2. `KubeDB-Provisioner` operator watches the `Qdrant` CR. +2. `KubeDB` provisioner operator watches for the `Qdrant` CR. -3. When the operator finds a `Qdrant` CR, it creates a `StatefulSet` and related necessary stuff like secrets, services, etc. +3. When the operator finds a `Qdrant` CR, it creates a `PetSet` and related necessary stuff like secret, service, etc. -4. Then, in order to update the CPU and memory resources of the `Qdrant` database nodes, the user creates a `QdrantOpsRequest` CR with the desired resource specifications. +4. Then, in order to update the resources(for example `CPU`, `Memory` etc.) of the `Qdrant` cluster the user creates a `QdrantOpsRequest` CR with desired information. -5. `KubeDB` Ops-manager operator watches the `QdrantOpsRequest` CR. +5. `KubeDB` Ops Manager watches for `QdrantOpsRequest`. -6. When it finds a `QdrantOpsRequest` CR, it pauses the `Qdrant` object so that the `KubeDB-Provisioner` operator doesn't perform any operations on the `Qdrant` during the scaling process. +6. When it finds one, it halts the `Qdrant` object so that the `KubeDB` provisioner operator doesn't perform any operation on the `Qdrant` during the scaling process. -7. Then the `KubeDB` Ops-manager operator updates the resources of the `StatefulSet` pods to the desired values defined in the `QdrantOpsRequest` CR. +7. Then the KubeDB Ops-manager operator will update resources of the PetSet's Pods to reach desired state. -8. After the successful resource update of the pods, the `KubeDB` Ops-manager updates the resource specifications in the `Qdrant` object to reflect the updated state. +8. After successful updating of the resources of the PetSet's Pods, the `KubeDB` Ops Manager updates the `Qdrant` object resources to reflect the updated state. -9. After the successful Vertical Scaling, the `KubeDB` Ops-manager resumes the `Qdrant` object so that the `KubeDB-Provisioner` resumes its usual operations. +9. After successful updating of the `Qdrant` resources, the `KubeDB` Ops Manager resumes the `Qdrant` object so that the `KubeDB` Provisioner operator resumes its usual operations. -In the next doc, we are going to show a step-by-step guide on Vertical Scaling of a Qdrant database using `QdrantOpsRequest` CRD. +In the next doc, we are going to show a step-by-step guide on updating resources of Qdrant database using vertical scaling operation. diff --git a/docs/guides/qdrant/scaling/vertical-scaling/vertical-scaling.md b/docs/guides/qdrant/scaling/vertical-scaling/vertical-scaling.md index 0054563b2..dd03e7750 100644 --- a/docs/guides/qdrant/scaling/vertical-scaling/vertical-scaling.md +++ b/docs/guides/qdrant/scaling/vertical-scaling/vertical-scaling.md @@ -34,9 +34,9 @@ $ kubectl create ns demo namespace/demo created ``` -> **Note:** YAML files used in this tutorial are stored in [docs/guides/qdrant/scaling/vertical-scaling/scale-vertically/yamls](/docs/guides/qdrant/scaling/vertical-scaling/scale-vertically/yamls) directory of [kubedb/docs](https://github.com/kubedb/docs) repository. +> **Note:** YAML files used in this tutorial are stored in [docs/examples/qdrant/scaling/vertical-scaling](/docs/examples/qdrant/scaling/vertical-scaling) directory of [kubedb/docs](https://github.com/kubedb/docs) repository. -### Apply Vertical Scaling on Qdrant Cluster +## Apply Vertical Scaling on Qdrant Cluster Here, we are going to deploy a `Qdrant` cluster using a supported version by `KubeDB` operator. Then we are going to apply vertical scaling on it. @@ -66,7 +66,7 @@ spec: Let's create the `Qdrant` CR we have shown above: ```bash -$ kubectl create -f https://github.com/kubedb/docs/raw/{{< param "info.version" >}}/docs/guides/qdrant/scaling/vertical-scaling/scale-vertically/yamls/qdrant.yaml +$ kubectl create -f https://github.com/kubedb/docs/raw/{{< param "info.version" >}}/docs/examples/qdrant/scaling/vertical-scaling/qdrant.yaml qdrant.kubedb.com/qdrant-sample created ``` @@ -149,7 +149,7 @@ Here, Let's create the `QdrantOpsRequest` CR we have shown above: ```bash -$ kubectl apply -f https://github.com/kubedb/docs/raw/{{< param "info.version" >}}/docs/guides/qdrant/scaling/vertical-scaling/scale-vertically/yamls/vscale.yaml +$ kubectl apply -f https://github.com/kubedb/docs/raw/{{< param "info.version" >}}/docs/examples/qdrant/scaling/vertical-scaling/vscale.yaml qdrantopsrequest.ops.kubedb.com/qdops-vscale created ``` diff --git a/docs/guides/qdrant/tls/overview.md b/docs/guides/qdrant/tls/overview.md index c6d6ff4e8..2fc173b2e 100644 --- a/docs/guides/qdrant/tls/overview.md +++ b/docs/guides/qdrant/tls/overview.md @@ -12,9 +12,9 @@ section_menu_id: guides > New to KubeDB? Please start [here](/docs/README.md). -# Qdrant TLS/SSL Encryption +# Qdrant TLS Encryption -This guide will give an overview of how KubeDB supports TLS/SSL encryption for `Qdrant` databases. +This guide will give an overview of how KubeDB supports TLS encryption for `Qdrant` databases. ## Before You Begin @@ -36,7 +36,7 @@ KubeDB uses `cert-manager` to manage TLS certificates for Qdrant databases. The 5. `cert-manager` creates the certificates and stores them in a `Secret`. -6. `KubeDB-Provisioner` operator creates the `StatefulSet` with the TLS secrets mounted, enabling encrypted communication. +6. `KubeDB-Provisioner` operator creates the `Petset` with the TLS secrets mounted, enabling encrypted communication. 7. The `Qdrant` database nodes use these certificates for encrypted client-to-server and peer-to-peer communication. diff --git a/docs/guides/qdrant/update-version/overview.md b/docs/guides/qdrant/update-version/overview.md index bbcd252c0..40d716a6c 100644 --- a/docs/guides/qdrant/update-version/overview.md +++ b/docs/guides/qdrant/update-version/overview.md @@ -1,5 +1,5 @@ --- -title: Updating Qdrant Version +title: Updating Qdrant Version Overview menu: docs_{{ .version }}: identifier: qdrant-update-version-overview @@ -12,9 +12,9 @@ section_menu_id: guides > New to KubeDB? Please start [here](/docs/README.md). -# Updating Qdrant Version +# Updating Qdrant Version Overview -This guide will give you an overview of how KubeDB Ops-manager updates the version of a `Qdrant` database. +This guide will give you an overview on how KubeDB Ops-manager operator update the version of `Qdrant` database. ## Before You Begin @@ -22,26 +22,33 @@ This guide will give you an overview of how KubeDB Ops-manager updates the versi - [Qdrant](/docs/guides/qdrant/concepts/qdrant.md) - [QdrantOpsRequest](/docs/guides/qdrant/concepts/opsrequest.md) -## How the Update Process Works +## How update version Process Works + +The following diagram shows how KubeDB Ops-manager operator update the version of `Qdrant`. Open the image in a new tab to see the enlarged version. + +
+  updating Process of Qdrant +
Fig: Updating Process of Qdrant
+
The updating process consists of the following steps: -1. At first, a user creates a `Qdrant` CR. +1. At first, a user creates a `Qdrant` Custom Resource (CR). -2. `KubeDB-Provisioner` operator watches for the `Qdrant` CR. +2. `KubeDB` Provisioner operator watches the `Qdrant` CR. -3. When it finds one, it creates a `StatefulSet` and related necessary stuff like secrets, services, etc. +3. When the operator finds a `Qdrant` CR, it creates `PetSets` and related necessary resources like secrets, services, etc. -4. Then, in order to update the version of the `Qdrant` database, the user creates a `QdrantOpsRequest` CR with the desired target version. +4. Then, in order to update the version of the `Qdrant` the user creates a `QdrantOpsRequest` CR with the desired version. -5. `KubeDB-ops-manager` operator watches for `QdrantOpsRequest`. +5. `KubeDB` Ops-manager operator watches the `QdrantOpsRequest` CR. -6. When it finds one, it pauses the `Qdrant` object so that the `KubeDB-Provisioner` operator doesn't perform any operations on the `Qdrant` during the updating process. +6. When it finds a `QdrantOpsRequest` CR, it halts the `Qdrant` object which is referred from the `QdrantOpsRequest`. So, the `KubeDB` Provisioner operator doesn't perform any operations on the `Qdrant` object during the updating process. -7. By looking at the target version from the `QdrantOpsRequest` CR, the `KubeDB-ops-manager` operator updates the images of the `StatefulSet` for the new version. +7. By looking at the target version from `QdrantOpsRequest` CR, `KubeDB` Ops-manager operator updates the images of the `PetSet`. -8. After successful update of the `StatefulSet` and its Pod images, the `KubeDB-ops-manager` updates the image of the `Qdrant` object to reflect the updated cluster state. +8. After successfully updating the `PetSet` and their `Pods` images, the `KubeDB` Ops-manager operator updates the image of the `Qdrant` object to reflect the updated state of the database. -9. After successful update of the `Qdrant` object, the `KubeDB` Ops-manager resumes the `Qdrant` object so that the `KubeDB-Provisioner` can resume its usual operations. +9. After successfully updating of `Qdrant` object, the `KubeDB` Ops-manager operator resumes the `Qdrant` object so that the `KubeDB` Provisioner operator can resume its usual operations. -In the next doc, we are going to show a step-by-step guide on updating a Qdrant database using the `UpdateVersion` operation. +In the next doc, we are going to show a step-by-step guide on updating of a Qdrant using UpdateVersion operation. diff --git a/docs/guides/qdrant/update-version/update-version.md b/docs/guides/qdrant/update-version/update-version.md index 5b28298a0..48e54fe4d 100644 --- a/docs/guides/qdrant/update-version/update-version.md +++ b/docs/guides/qdrant/update-version/update-version.md @@ -34,13 +34,13 @@ $ kubectl create ns demo namespace/demo created ``` -> **Note:** YAML files used in this tutorial are stored in [docs/guides/qdrant/update-version/versionupgrading/yamls](/docs/guides/qdrant/update-version/versionupgrading/yamls) directory of [kubedb/docs](https://github.com/kubedb/docs) repository. +> **Note:** YAML files used in this tutorial are stored in [docs/examples/qdrant/update-version](/docs/examples/qdrant/update-version) directory of [kubedb/docs](https://github.com/kubedb/docs) repository. -### Apply Version Updating on Qdrant +## Apply Version Updating on Qdrant Here, we are going to deploy a `Qdrant` instance using a supported version by `KubeDB` provisioner. Then we are going to apply update-ops-request on it. -#### Prepare Qdrant +### Prepare Qdrant At first, we are going to deploy a Qdrant instance using a supported `Qdrant` version. In the next two sections, we are going to find out the supported versions and version update constraints. @@ -50,31 +50,22 @@ When you have installed `KubeDB`, it has created `QdrantVersion` CR for all supp ```bash $ kubectl get qdrantversion -NAME VERSION DB_IMAGE DEPRECATED AGE -1.7.4 1.7.4 qdrant/qdrant:v1.7.4 5m -1.8.4 1.8.4 qdrant/qdrant:v1.8.4 5m -1.9.7 1.9.7 qdrant/qdrant:v1.9.7 5m -1.10.1 1.10.1 qdrant/qdrant:v1.10.1 5m -1.11.5 1.11.5 qdrant/qdrant:v1.11.5 5m -1.12.6 1.12.6 qdrant/qdrant:v1.12.6 5m -1.13.4 1.13.4 qdrant/qdrant:v1.13.4 5m -1.14.1 1.14.1 qdrant/qdrant:v1.14.1 5m -1.15.1 1.15.1 qdrant/qdrant:v1.15.1 5m -1.16.1 1.16.1 qdrant/qdrant:v1.16.1 5m -1.17.0 1.17.0 qdrant/qdrant:v1.17.0 5m -1.18.0 1.18.0 qdrant/qdrant:v1.18.0 5m +NAME VERSION DB_IMAGE DEPRECATED AGE +1.15.4 1.15.4 docker.io/qdrant/qdrant:v1.15.4-unprivileged 24d +1.16.2 1.16.2 docker.io/qdrant/qdrant:v1.16.2-unprivileged 24d +1.17.0 1.17.0 docker.io/qdrant/qdrant:v1.17.0-unprivileged 24d ``` The version above that does not show `DEPRECATED` `true` is supported by `KubeDB` for `Qdrant`. You can use any non-deprecated version. Now, we are going to select a non-deprecated version from `QdrantVersion` for the `Qdrant` instance that we will update from this version to another. In the next section, we are going to verify version update constraints. **Check update Constraints:** -Qdrant supports rolling version updates. You can update from any currently running version to a newer patch or minor version. Major version jumps should follow the Qdrant upstream upgrade notes. For example, you can update directly from `1.17.0` to `1.18.0`. +Qdrant supports rolling version updates. You can update from any currently running version to a newer patch or minor version. Major version jumps should follow the Qdrant upstream upgrade notes. For example, you can update directly from `1.16.2` to `1.17.0`. Let's get one of the `qdrantversion` YAMLs: ```bash -$ kubectl get qdrantversion 1.17.0 -o yaml | kubectl neat +$ kubectl get qdrantversion 1.17.0 -o yaml apiVersion: catalog.kubedb.com/v1alpha1 kind: QdrantVersion metadata: @@ -85,7 +76,7 @@ metadata: name: "1.17.0" spec: db: - image: qdrant/qdrant:v1.17.0 + image: docker.io/qdrant/qdrant:v1.17.0-unprivileged version: "1.17.0" ``` @@ -100,7 +91,7 @@ metadata: name: qdrant-sample namespace: demo spec: - version: "1.17.0" + version: "1.16.2" replicas: 3 storage: accessModes: @@ -114,7 +105,7 @@ spec: Let's create the `Qdrant` cr we have shown above: ```bash -$ kubectl create -f https://github.com/kubedb/docs/raw/{{< param "info.version" >}}/docs/guides/qdrant/update-version/versionupgrading/yamls/qdrant.yaml +$ kubectl create -f https://github.com/kubedb/docs/raw/{{< param "info.version" >}}/docs/examples/qdrant/update-version/qdrant.yaml qdrant.kubedb.com/qdrant-sample created ``` @@ -129,7 +120,7 @@ $ watch -n 3 kubectl get qdrant -n demo Every 3.0s: kubectl get qdrant -n demo NAME VERSION STATUS AGE -qdrant-sample 1.17.0 Ready 3m42s +qdrant-sample 1.16.2 Ready 3m42s $ watch -n 3 kubectl get petset -n demo qdrant-sample Every 3.0s: kubectl get petset -n demo qdrant-sample @@ -150,20 +141,19 @@ Let's verify the `Qdrant`, the `PetSet` and its `Pod` image version: ```bash $ kubectl get qdrant -n demo qdrant-sample -o=jsonpath='{.spec.version}{"\n"}' -1.17.0 +1.16.2 $ kubectl get petset -n demo qdrant-sample -o=jsonpath='{.spec.template.spec.containers[0].image}{"\n"}' -qdrant/qdrant:v1.17.0 +docker.io/qdrant/qdrant:v1.16.2-unprivileged $ kubectl get pod -n demo qdrant-sample-0 -o=jsonpath='{.spec.containers[0].image}{"\n"}' -qdrant/qdrant:v1.17.0 -``` +docker.io/qdrant/qdrant:v1.16.2-unprivileged We are ready to apply version updating on this `Qdrant` instance. -#### UpdateVersion +### UpdateVersion -Here, we are going to update `Qdrant` from version `1.17.0` to `1.18.0`. +Here, we are going to update `Qdrant` from version `1.16.2` to `1.17.0`. **Create QdrantOpsRequest:** @@ -178,7 +168,7 @@ metadata: spec: type: UpdateVersion updateVersion: - targetVersion: "1.18.0" + targetVersion: "1.17.0" databaseRef: name: qdrant-sample timeout: 5m @@ -189,18 +179,18 @@ Here, - `spec.databaseRef.name` specifies that we are performing operation on `qdrant-sample` Qdrant database. - `spec.type` specifies that we are going to perform `UpdateVersion` on our database. -- `spec.updateVersion.targetVersion` specifies the expected version `1.18.0` after updating. +- `spec.updateVersion.targetVersion` specifies the expected version `1.17.0` after updating. - `spec.timeout` specifies the timeout for the operation (learn more [here](/docs/guides/qdrant/concepts/opsrequest.md#spectimeout)). - `spec.apply` specifies when to apply the operation (learn more [here](/docs/guides/qdrant/concepts/opsrequest.md#specapply)). Let's create the `QdrantOpsRequest` cr we have shown above: ```bash -$ kubectl apply -f https://github.com/kubedb/docs/raw/{{< param "info.version" >}}/docs/guides/qdrant/update-version/versionupgrading/yamls/update_version.yaml +$ kubectl apply -f https://github.com/kubedb/docs/raw/{{< param "info.version" >}}/docs/examples/qdrant/update-version/ops-request.yaml qdrantopsrequest.ops.kubedb.com/qdops-update-version created ``` -**Verify Qdrant version updated successfully:** +#### Verify Qdrant version updated successfully If everything goes well, `KubeDB` ops-manager operator will update the image of `Qdrant`, `PetSet`, and its `Pod`. @@ -224,34 +214,87 @@ Labels: Annotations: API Version: ops.kubedb.com/v1alpha1 Kind: QdrantOpsRequest +Metadata: + Creation Timestamp: 2026-05-15T05:36:46Z + Generation: 1 + Resource Version: 3356440 + UID: 156625b9-5f36-465e-aa88-ff0e5452c7ba Spec: + Apply: IfReady Database Ref: - Name: qdrant-sample - Type: UpdateVersion + Name: qdrant-sample + Max Retries: 1 + Timeout: 5m + Type: UpdateVersion Update Version: - Target Version: 1.18.0 + Target Version: 1.17.0 Status: Conditions: - Last Transition Time: 2026-05-01T10:00:00Z - Message: Qdrant ops request is updating version + Last Transition Time: 2026-05-15T05:36:46Z + Message: Qdrant ops-request has started to update version Observed Generation: 1 Reason: UpdateVersion Status: True Type: UpdateVersion - Last Transition Time: 2026-05-01T10:00:05Z - Message: Successfully updated PetSets update strategy type + Last Transition Time: 2026-05-15T05:37:01Z + Message: successfully reconciled the Qdrant with updated version Observed Generation: 1 Reason: UpdatePetSets Status: True Type: UpdatePetSets - Last Transition Time: 2026-05-01T10:00:30Z - Message: Successfully updated pod images + Last Transition Time: 2026-05-15T05:38:10Z + Message: Successfully Restarted Qdrant nodes + Observed Generation: 1 + Reason: RestartPods + Status: True + Type: RestartPods + Last Transition Time: 2026-05-15T05:37:15Z + Message: get pod; ConditionStatus:True; PodName:qdrant-sample-0 + Observed Generation: 1 + Status: True + Type: GetPod--qdrant-sample-0 + Last Transition Time: 2026-05-15T05:37:17Z + Message: evict pod; ConditionStatus:True; PodName:qdrant-sample-0 + Observed Generation: 1 + Status: True + Type: EvictPod--qdrant-sample-0 + Last Transition Time: 2026-05-15T05:37:45Z + Message: running pod; ConditionStatus:True; PodName:qdrant-sample-0 + Observed Generation: 1 + Status: True + Type: RunningPod--qdrant-sample-0 + Last Transition Time: 2026-05-15T05:37:30Z + Message: get pod; ConditionStatus:True; PodName:qdrant-sample-1 + Observed Generation: 1 + Status: True + Type: GetPod--qdrant-sample-1 + Last Transition Time: 2026-05-15T05:37:31Z + Message: evict pod; ConditionStatus:True; PodName:qdrant-sample-1 + Observed Generation: 1 + Status: True + Type: EvictPod--qdrant-sample-1 + Last Transition Time: 2026-05-15T05:37:45Z + Message: running pod; ConditionStatus:True; PodName:qdrant-sample-1 + Observed Generation: 1 + Status: True + Type: RunningPod--qdrant-sample-1 + Last Transition Time: 2026-05-15T05:37:50Z + Message: get pod; ConditionStatus:True; PodName:qdrant-sample-2 + Observed Generation: 1 + Status: True + Type: GetPod--qdrant-sample-2 + Last Transition Time: 2026-05-15T05:37:52Z + Message: evict pod; ConditionStatus:True; PodName:qdrant-sample-2 + Observed Generation: 1 + Status: True + Type: EvictPod--qdrant-sample-2 + Last Transition Time: 2026-05-15T05:38:05Z + Message: running pod; ConditionStatus:True; PodName:qdrant-sample-2 Observed Generation: 1 - Reason: UpdatePetSetImage Status: True - Type: UpdatePetSetImage - Last Transition Time: 2026-05-01T10:02:45Z - Message: Successfully completed the modification process. + Type: RunningPod--qdrant-sample-2 + Last Transition Time: 2026-05-15T05:38:11Z + Message: Successfully updated Qdrant version Observed Generation: 1 Reason: Successful Status: True @@ -259,29 +302,29 @@ Status: Observed Generation: 1 Phase: Successful Events: - Type Reason Age From Message - ---- ------ ---- ---- ------- - Normal PauseDatabase 3m12s KubeDB Enterprise Operator Pausing Qdrant demo/qdrant-sample - Normal PauseDatabase 3m12s KubeDB Enterprise Operator Successfully paused Qdrant demo/qdrant-sample - Normal Updating 3m12s KubeDB Enterprise Operator Updating PetSets - Normal Updating 3m12s KubeDB Enterprise Operator Successfully Updated PetSets - Normal UpdatePetSetImage 1m10s KubeDB Enterprise Operator Successfully Updated pod images - Normal ResumeDatabase 1m10s KubeDB Enterprise Operator Resuming Qdrant demo/qdrant-sample - Normal ResumeDatabase 1m10s KubeDB Enterprise Operator Successfully resumed Qdrant demo/qdrant-sample - Normal Successful 1m10s KubeDB Enterprise Operator Successfully Updated Database + Type Reason Age From Message + ---- ------ ---- ---- ------- + Normal Starting 86s KubeDB Ops-manager Operator Pausing Qdrant database demo/qdrant-sample + Normal Successful 86s KubeDB Ops-manager Operator Successfully paused Qdrant database: demo/qdrant-sample for QdrantOpsRequest: qdops-update-version + Normal UpdatePetSets 74s KubeDB Ops-manager Operator successfully reconciled the Qdrant with updated version + Warning get pod; ConditionStatus:True; PodName:qdrant-sample-0 60s KubeDB Ops-manager Operator get pod; ConditionStatus:True; PodName:qdrant-sample-0 + Warning evict pod; ConditionStatus:True; PodName:qdrant-sample-0 58s KubeDB Ops-manager Operator evict pod; ConditionStatus:True; PodName:qdrant-sample-0 + Normal RestartPods 5s KubeDB Ops-manager Operator Successfully Restarted Qdrant nodes + Normal Starting 4s KubeDB Ops-manager Operator Resuming Qdrant database: demo/qdrant-sample + Normal Successful 4s KubeDB Ops-manager Operator Successfully resumed Qdrant database: demo/qdrant-sample for QdrantOpsRequest: qdops-update-version ``` Now, we are going to verify whether the `Qdrant`, `PetSet` and its `Pod` have updated with the new image. Let's check: ```bash $ kubectl get qdrant -n demo qdrant-sample -o=jsonpath='{.spec.version}{"\n"}' -1.18.0 +1.17.0 $ kubectl get petset -n demo qdrant-sample -o=jsonpath='{.spec.template.spec.containers[0].image}{"\n"}' -qdrant/qdrant:v1.18.0 +docker.io/qdrant/qdrant:v1.17.0-unprivileged $ kubectl get pod -n demo qdrant-sample-0 -o=jsonpath='{.spec.containers[0].image}{"\n"}' -qdrant/qdrant:v1.18.0 +docker.io/qdrant/qdrant:v1.17.0-unprivileged ``` You can see above that our `Qdrant` has been updated with the new version. It verifies that we have successfully updated our Qdrant instance. @@ -297,7 +340,12 @@ You can see above that our `Qdrant` has been updated with the new version. It ve To clean up the Kubernetes resources created by this tutorial, run: ```bash -kubectl delete qdrant -n demo qdrant-sample -kubectl delete QdrantOpsRequest -n demo qdops-update-version -kubectl delete ns demo +$ kubectl delete qdrant -n demo qdrant-sample +qdrant.kubedb.com "qdrant-sample" deleted + +$ kubectl delete qdrantopsrequest -n demo qdops-update-version +qdrantopsrequest.ops.kubedb.com "qdops-update-version" deleted + +$ kubectl delete ns demo +namespace "demo" deleted ``` \ No newline at end of file diff --git a/docs/guides/qdrant/volume-expansion/overview.md b/docs/guides/qdrant/volume-expansion/overview.md index ce719e74c..957b4e8f0 100644 --- a/docs/guides/qdrant/volume-expansion/overview.md +++ b/docs/guides/qdrant/volume-expansion/overview.md @@ -22,7 +22,14 @@ This guide will give an overview of how KubeDB Ops-manager expands the volume of - [Qdrant](/docs/guides/qdrant/concepts/qdrant.md) - [QdrantOpsRequest](/docs/guides/qdrant/concepts/opsrequest.md) -## How Volume Expansion Works +## How Volume Expansion Process Works + +The following diagram shows how KubeDB Ops Manager expand the volumes of `Qdrant` database components. Open the image in a new tab to see the enlarged version. + +
+  Volume Expansion process of Qdrant +
Fig: Volume Expansion process of Qdrant
+
The Volume Expansion process consists of the following steps: @@ -30,9 +37,9 @@ The Volume Expansion process consists of the following steps: 2. `KubeDB-Provisioner` operator watches the `Qdrant` CR. -3. When the operator finds a `Qdrant` CR, it creates a `StatefulSet` and related necessary stuff like pods, PVCs, secrets, services, etc. +3. When the operator finds a `Qdrant` CR, it creates a `Petset` and related necessary stuff like pods, PVCs, secrets, services, etc. -4. Each StatefulSet creates a Persistent Volume according to the volume claim template. This Persistent Volume will be expanded by the `KubeDB` Ops-manager operator. +4. Each Petset creates a Persistent Volume according to the volume claim template. This Persistent Volume will be expanded by the `KubeDB` Ops-manager operator. 5. Then, in order to expand the volume of the `Qdrant` database, the user creates a `QdrantOpsRequest` CR with the desired new storage size. diff --git a/docs/guides/qdrant/volume-expansion/volume-expansion.md b/docs/guides/qdrant/volume-expansion/volume-expansion.md index 0caf5b136..8c81de9f6 100644 --- a/docs/guides/qdrant/volume-expansion/volume-expansion.md +++ b/docs/guides/qdrant/volume-expansion/volume-expansion.md @@ -5,7 +5,7 @@ menu: identifier: qdrant-volume-expansion-cluster name: Cluster parent: qdrant-volume-expansion - weight: 10 + weight: 20 menu_name: docs_{{ .version }} section_menu_id: guides --- @@ -36,7 +36,7 @@ $ kubectl create ns demo namespace/demo created ``` -> **Note:** YAML files used in this tutorial are stored in [docs/guides/qdrant/volume-expansion/yamls](/docs/guides/qdrant/volume-expansion/yamls) directory of [kubedb/docs](https://github.com/kubedb/docs) repository. +> **Note:** YAML files used in this tutorial are stored in [docs/examples/qdrant/volume-expansion](/docs/examples/qdrant/volume-expansion) directory of [kubedb/docs](https://github.com/kubedb/docs) repository. ## Expand Volume of Qdrant Database @@ -48,12 +48,13 @@ At first verify that your cluster has a storage class that supports volume expan ```bash $ kubectl get storageclass -NAME PROVISIONER RECLAIMPOLICY VOLUMEBINDINGMODE ALLOWVOLUMEEXPANSION AGE -standard (default) rancher.io/local-path Delete WaitForFirstConsumer false 5m -standard-expandable kubernetes.io/gce-pd Delete Immediate true 5m +NAME PROVISIONER RECLAIMPOLICY VOLUMEBINDINGMODE ALLOWVOLUMEEXPANSION AGE +local-path (default) rancher.io/local-path Delete WaitForFirstConsumer false 2d +longhorn (default) driver.longhorn.io Delete Immediate true 3m25s +longhorn-static driver.longhorn.io Delete Immediate true 3m19s ``` -We can see the `standard-expandable` storage class has `ALLOWVOLUMEEXPANSION` field as `true`. So, this storage class supports volume expansion. We can use it. +We can see from the output that `longhorn (default)` storage class has `ALLOWVOLUMEEXPANSION` field as true. So, this storage class supports volume expansion. We will use this storage class. Now, we are going to deploy a `Qdrant` cluster with version `1.17.0`. @@ -71,7 +72,7 @@ spec: version: "1.17.0" replicas: 3 storage: - storageClassName: "standard-expandable" + storageClassName: "longhorn" accessModes: - ReadWriteOnce resources: @@ -83,7 +84,7 @@ spec: Let's create the `Qdrant` CR we have shown above, ```bash -$ kubectl create -f https://github.com/kubedb/docs/raw/{{< param "info.version" >}}/docs/guides/qdrant/volume-expansion/yamls/qdrant.yaml +$ kubectl create -f https://github.com/kubedb/docs/raw/{{< param "info.version" >}}/docs/examples/qdrant/volume-expansion/qdrant.yaml qdrant.kubedb.com/qdrant-sample created ``` @@ -101,14 +102,14 @@ Let's check volume size from the PetSet and from the persistent volumes: $ kubectl get petset -n demo qdrant-sample -o json | jq '.spec.volumeClaimTemplates[].spec.resources.requests.storage' "1Gi" -$ kubectl get pvc -n demo -NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE -qdrant-sample-qdrant-sample-0 Bound pvc-a1b2c3d4-e5f6-7890-abcd-ef1234567890 1Gi RWO standard-expandable 4m -qdrant-sample-qdrant-sample-1 Bound pvc-b2c3d4e5-f6a7-8901-bcde-f01234567891 1Gi RWO standard-expandable 3m -qdrant-sample-qdrant-sample-2 Bound pvc-c3d4e5f6-a7b8-9012-cdef-012345678902 1Gi RWO standard-expandable 2m +$ kubectl get pv -n demo +NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS VOLUMEATTRIBUTESCLASS AGE +pvc-0e300ccf-49f1-4e11-b630-bc3756baeaa0 1Gi RWO Delete Bound demo/data-qdrant-sample-0 longhorn 4m +pvc-20ab1d50-23d7-409a-ba2e-759250f9f758 1Gi RWO Delete Bound demo/data-qdrant-sample-2 longhorn 4m +pvc-ccee01bf-9551-4efc-8945-5a3d25c60c7b 1Gi RWO Delete Bound demo/data-qdrant-sample-1 longhorn 4m ``` -You can see the PetSet has 1Gi storage, and the capacity of the persistent volumes is also 1Gi. +You can see the PetSet has 1Gi storage, and the capacity of all the persistent volumes are also 1Gi. We are now ready to apply the `QdrantOpsRequest` CR to expand the volume of this database. @@ -132,7 +133,7 @@ spec: name: qdrant-sample type: VolumeExpansion volumeExpansion: - mode: Online + mode: "Offline" node: 3Gi ``` @@ -141,16 +142,16 @@ Here, - `spec.databaseRef.name` specifies that we are performing volume expansion operation on `qdrant-sample` Qdrant database. - `spec.type` specifies that we are performing `VolumeExpansion` on our database. - `spec.volumeExpansion.node` specifies the desired volume size. -- `spec.volumeExpansion.mode` specifies the desired volume expansion mode (`Online` or `Offline`). +- `spec.volumeExpansion.mode` specifies the desired volume expansion mode (`Online` or `Offline`). Storageclass `longhorn` supports `Offline` volume expansion. -> Note: If the StorageClass doesn't support `Online` volume expansion, try offline volume expansion by using `spec.volumeExpansion.mode: "Offline"`. +> **Note:** If the Storageclass you are using support `Online` Volume Expansion, Try Online volume expansion by using `spec.volumeExpansion.mode:"Online"`. -During `Online` VolumeExpansion KubeDB expands volume without pausing the database object; it directly updates the underlying PVC. For `Offline` volume expansion, the database is paused, the Pods are deleted, the PVC is updated, and then the database Pods are recreated with the updated PVC. +During `Online` VolumeExpansion KubeDB expands volume without deleting the pods, it directly updates the underlying PVC. And for Offline volume expansion, the database is paused. The Pods are deleted and PVC is updated. Then the database Pods are recreated with updated PVC. Let's create the `QdrantOpsRequest` CR we have shown above, ```bash -$ kubectl apply -f https://github.com/kubedb/docs/raw/{{< param "info.version" >}}/docs/guides/qdrant/volume-expansion/yamls/qdops-vol-exp.yaml +$ kubectl apply -f https://github.com/kubedb/docs/raw/{{< param "info.version" >}}/docs/examples/qdrant/volume-expansion/ops-request.yaml qdrantopsrequest.ops.kubedb.com/qdops-vol-exp created ``` @@ -179,33 +180,91 @@ Kind: QdrantOpsRequest Spec: Apply: IfReady Database Ref: - Name: qdrant-sample - Type: VolumeExpansion + Name: qdrant-sample + Max Retries: 1 + Type: VolumeExpansion Volume Expansion: - Mode: Online + Mode: Offline Node: 3Gi Status: Conditions: - Last Transition Time: 2026-05-01T10:04:19Z - Message: Qdrant ops request is expanding volume of database - Observed Generation: 1 - Reason: Running - Status: True - Type: Running - Last Transition Time: 2026-05-01T10:05:12Z - Message: Online Volume Expansion performed successfully in Qdrant pods for QdrantOpsRequest: demo/qdops-vol-exp + Last Transition Time: 2026-05-15T05:20:42Z + Message: Qdrant ops-request has started to expand volume of Qdrant nodes Observed Generation: 1 Reason: VolumeExpansion Status: True Type: VolumeExpansion - Last Transition Time: 2026-05-01T10:06:08Z + Last Transition Time: 2026-05-15T05:21:04Z + Message: successfully deleted the petSets with orphan propagation policy + Observed Generation: 1 + Reason: OrphanPetSetPods + Status: True + Type: OrphanPetSetPods + Last Transition Time: 2026-05-15T05:20:54Z + Message: get petset; ConditionStatus:True + Observed Generation: 1 + Status: True + Type: GetPetset + Last Transition Time: 2026-05-15T05:20:54Z + Message: delete petset; ConditionStatus:True + Observed Generation: 1 + Status: True + Type: DeletePetset + Last Transition Time: 2026-05-15T05:23:15Z + Message: successfully updated node PVC sizes + Observed Generation: 1 + Reason: UpdateNodePVCs + Status: True + Type: UpdateNodePVCs + Last Transition Time: 2026-05-15T05:23:10Z + Message: get pod; ConditionStatus:True + Observed Generation: 1 + Status: True + Type: GetPod + Last Transition Time: 2026-05-15T05:21:15Z + Message: patch ops request; ConditionStatus:True + Observed Generation: 1 + Status: True + Type: PatchOpsRequest + Last Transition Time: 2026-05-15T05:21:15Z + Message: delete pod; ConditionStatus:True + Observed Generation: 1 + Status: True + Type: DeletePod + Last Transition Time: 2026-05-15T05:21:25Z + Message: get pvc; ConditionStatus:True + Observed Generation: 1 + Status: True + Type: GetPvc + Last Transition Time: 2026-05-15T05:21:26Z + Message: patch pvc; ConditionStatus:True + Observed Generation: 1 + Status: True + Type: PatchPvc + Last Transition Time: 2026-05-15T05:23:05Z + Message: compare storage; ConditionStatus:True + Observed Generation: 1 + Status: True + Type: CompareStorage + Last Transition Time: 2026-05-15T05:21:45Z + Message: create pod; ConditionStatus:True + Observed Generation: 1 + Status: True + Type: CreatePod + Last Transition Time: 2026-05-15T05:23:25Z + Message: successfully reconciled the Qdrant resources + Observed Generation: 1 + Reason: UpdatePetSets + Status: True + Type: UpdatePetSets + Last Transition Time: 2026-05-15T05:23:36Z Message: PetSet is recreated Observed Generation: 1 Reason: ReadyPetSets Status: True Type: ReadyPetSets - Last Transition Time: 2026-05-01T10:06:52Z - Message: Successfully Expanded Volume. + Last Transition Time: 2026-05-15T05:23:36Z + Message: Successfully completed volumeExpansion for Qdrant Observed Generation: 1 Reason: Successful Status: True @@ -213,15 +272,20 @@ Status: Observed Generation: 1 Phase: Successful Events: - Type Reason Age From Message - ---- ------ ---- ---- ------- - Normal PauseDatabase 12m KubeDB Ops-manager Operator Pausing Qdrant demo/qdrant-sample - Normal PauseDatabase 12m KubeDB Ops-manager Operator Successfully paused Qdrant demo/qdrant-sample - Normal VolumeExpansion 11m KubeDB Ops-manager Operator Online Volume Expansion performed successfully in Qdrant pods for QdrantOpsRequest: demo/qdops-vol-exp - Normal ResumeDatabase 11m KubeDB Ops-manager Operator Resuming Qdrant demo/qdrant-sample - Normal ResumeDatabase 11m KubeDB Ops-manager Operator Successfully resumed Qdrant demo/qdrant-sample - Normal ReadyPetSets 10m KubeDB Ops-manager Operator PetSet is recreated - Normal Successful 10m KubeDB Ops-manager Operator Successfully Expanded Volume + Type Reason Age From Message + ---- ------ ---- ---- ------- + Normal Starting 3m KubeDB Ops-manager Operator Pausing Qdrant database demo/qdrant-sample + Normal Successful 3m KubeDB Ops-manager Operator Successfully paused Qdrant database: demo/qdrant-sample for QdrantOpsRequest: qdops-vol-exp + Warning delete petset 3m KubeDB Ops-manager Operator delete petset; ConditionStatus:True + Normal OrphanPetSetPods 3m KubeDB Ops-manager Operator successfully deleted the petSets with orphan propagation policy + Warning delete pod 2m KubeDB Ops-manager Operator delete pod; ConditionStatus:True + Warning patch pvc 2m KubeDB Ops-manager Operator patch pvc; ConditionStatus:True + Warning create pod 1m KubeDB Ops-manager Operator create pod; ConditionStatus:True + Normal UpdateNodePVCs 1m KubeDB Ops-manager Operator successfully updated node PVC sizes + Normal UpdatePetSets 30s KubeDB Ops-manager Operator successfully reconciled the Qdrant resources + Normal ReadyPetSets 10s KubeDB Ops-manager Operator PetSet is recreated + Normal Starting 10s KubeDB Ops-manager Operator Resuming Qdrant database: demo/qdrant-sample + Normal Successful 10s KubeDB Ops-manager Operator Successfully resumed Qdrant database: demo/qdrant-sample for QdrantOpsRequest: qdops-vol-exp ``` Now, we are going to verify from the `PetSet` and `Persistent Volumes` whether the volume of the Qdrant database has expanded to meet the desired state: @@ -230,11 +294,11 @@ Now, we are going to verify from the `PetSet` and `Persistent Volumes` whether t $ kubectl get petset -n demo qdrant-sample -o json | jq '.spec.volumeClaimTemplates[].spec.resources.requests.storage' "3Gi" -$ kubectl get pvc -n demo -NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE -qdrant-sample-qdrant-sample-0 Bound pvc-a1b2c3d4-e5f6-7890-abcd-ef1234567890 3Gi RWO standard-expandable 14m -qdrant-sample-qdrant-sample-1 Bound pvc-b2c3d4e5-f6a7-8901-bcde-f01234567891 3Gi RWO standard-expandable 13m -qdrant-sample-qdrant-sample-2 Bound pvc-c3d4e5f6-a7b8-9012-cdef-012345678902 3Gi RWO standard-expandable 12m +$ kubectl get pv -n demo +NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS VOLUMEATTRIBUTESCLASS AGE +pvc-0e300ccf-49f1-4e11-b630-bc3756baeaa0 3Gi RWO Delete Bound demo/data-qdrant-sample-0 longhorn 5m +pvc-20ab1d50-23d7-409a-ba2e-759250f9f758 3Gi RWO Delete Bound demo/data-qdrant-sample-2 longhorn 5m +pvc-ccee01bf-9551-4efc-8945-5a3d25c60c7b 3Gi RWO Delete Bound demo/data-qdrant-sample-1 longhorn 5m ``` The above output verifies that we have successfully expanded the volume of the Qdrant database. From 8ce6e89cd3a6318ead298eee8fc819dbf7993449 Mon Sep 17 00:00:00 2001 From: Abdullah Al Masood <98589987+al-masood@users.noreply.github.com> Date: Tue, 19 May 2026 12:09:57 +0600 Subject: [PATCH 09/10] autoscaler guide completed Signed-off-by: Abdullah Al Masood <98589987+al-masood@users.noreply.github.com> --- ca.crt | 19 ++ ca.key | 28 +++ ...autoscaler.yaml => qdrant-as-compute.yaml} | 12 +- .../qdrant/autoscaler/compute/qdrant.yaml | 28 +++ ...autoscaler.yaml => qdrant-as-storage.yaml} | 11 +- .../qdrant/autoscaler/storage/qdrant.yaml | 17 ++ .../qdrant/reconfigure-tls/add-tls.yaml | 25 ++ .../qdrant/reconfigure-tls/issuer.yaml | 8 + .../qdrant/reconfigure-tls/qdrant.yaml | 15 ++ .../qdrant/reconfigure-tls/remove-tls.yaml | 11 + .../qdrant/reconfigure-tls/rotate-tls.yaml | 11 + docs/examples/qdrant/tls/issuer.yaml | 8 + docs/examples/qdrant/tls/tls-qdrant.yaml | 23 ++ .../autoscaler/compute/compute-autoscale.md | 54 ++-- .../autoscaler/storage/storage-autoscale.md | 78 +++--- docs/guides/qdrant/concepts/_index.md | 153 ------------ docs/guides/qdrant/concepts/autoscaler.md | 33 ++- docs/guides/qdrant/concepts/catalog.md | 37 ++- docs/guides/qdrant/concepts/opsrequest.md | 78 +++++- docs/guides/qdrant/concepts/qdrant.md | 236 ++++++++++++++++++ docs/guides/qdrant/images/qdrant-tls.png | Bin 0 -> 57000 bytes .../guides/qdrant/reconfigure-tls/overview.md | 36 ++- .../qdrant/reconfigure-tls/reconfigure-tls.md | 12 +- docs/guides/qdrant/tls/configure-tls.md | 51 ++-- docs/guides/qdrant/tls/overview.md | 73 ++++-- 25 files changed, 750 insertions(+), 307 deletions(-) create mode 100644 ca.crt create mode 100644 ca.key rename docs/examples/qdrant/autoscaler/compute/{autoscaler.yaml => qdrant-as-compute.yaml} (64%) create mode 100644 docs/examples/qdrant/autoscaler/compute/qdrant.yaml rename docs/examples/qdrant/autoscaler/storage/{autoscaler.yaml => qdrant-as-storage.yaml} (53%) create mode 100644 docs/examples/qdrant/autoscaler/storage/qdrant.yaml create mode 100644 docs/examples/qdrant/reconfigure-tls/add-tls.yaml create mode 100644 docs/examples/qdrant/reconfigure-tls/issuer.yaml create mode 100644 docs/examples/qdrant/reconfigure-tls/qdrant.yaml create mode 100644 docs/examples/qdrant/reconfigure-tls/remove-tls.yaml create mode 100644 docs/examples/qdrant/reconfigure-tls/rotate-tls.yaml create mode 100644 docs/examples/qdrant/tls/issuer.yaml create mode 100644 docs/examples/qdrant/tls/tls-qdrant.yaml create mode 100644 docs/guides/qdrant/concepts/qdrant.md create mode 100644 docs/guides/qdrant/images/qdrant-tls.png diff --git a/ca.crt b/ca.crt new file mode 100644 index 000000000..277bd3064 --- /dev/null +++ b/ca.crt @@ -0,0 +1,19 @@ +-----BEGIN CERTIFICATE----- +MIIDJTCCAg2gAwIBAgIUSq2Ks96UnmxPg7646uNhdJXpvC8wDQYJKoZIhvcNAQEL +BQAwIjEPMA0GA1UEAwwGcWRyYW50MQ8wDQYDVQQKDAZrdWJlZGIwHhcNMjYwNTE4 +MDgwODU1WhcNMjcwNTE4MDgwODU1WjAiMQ8wDQYDVQQDDAZxZHJhbnQxDzANBgNV +BAoMBmt1YmVkYjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALcmKSZa +Nos6KlwCCJogYZY9/Wo6Lt0+rqc9OgbbMk6RP4teEIcyEOfKjHmnLiBPKPhfPJkX +CPCynNbw/MAKq4Y0q2pLzT1RcY0AJMWXdLoAEEtnxHDXvZUIjWjeJ8Eo0VFKIxvR +IyVL+wSfanMU3gI9Wzx2hwT7DZG6CqZnFLq/gXx/0GpAE6CzJHejjp3T1Hhwesbo +mnZxPgf54+qKVxQ0jb5fygh6SfZ4rmLvOBemBXzt09vlEjtMz5UOa2DuN6vL/yTE +4YExM6ULPdUJrzJVSQkCF7uM4Lc5SCRrcnaSjkzg9JBVIrWybGS2Bdz7Z3Cqv9r6 +jzdpJppM7E/3IekCAwEAAaNTMFEwHQYDVR0OBBYEFOdPZ7nipiw08sy5tHDKcJVY +dF/AMB8GA1UdIwQYMBaAFOdPZ7nipiw08sy5tHDKcJVYdF/AMA8GA1UdEwEB/wQF +MAMBAf8wDQYJKoZIhvcNAQELBQADggEBAAyprbEOEyeNGC0Vki7o61M6cmhsliSZ +NX36ofQlq7EpZqtOYFm3LgsJXkponD8HUY+A6A2ingymDRHjzDwgFk4adnbzptIC +hF3jaLHtuzzkPDtW+MLb5xQ5U9adNq3+vMRfA7zhg+2PIwR+ENxFblHgOZySni57 +iUNVPG7lfSrd5YNSqx8+80mLWV0lt8Mnwsv/yBHqJ3Ab/HwbOKwTL8MGtKkYG0wO +bPgGkLhNHK7QUu2XwJLZiP8M/dsaGBUYvDCigscHLMIgIBfRo2OALWBHxOZbCx5u +czVeZTXo5bAEER8Wg3KJD06zZWqVybvQXIIcb/bLW7tMUmtAkrxIa/U= +-----END CERTIFICATE----- diff --git a/ca.key b/ca.key new file mode 100644 index 000000000..888892ddf --- /dev/null +++ b/ca.key @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQC3JikmWjaLOipc +AgiaIGGWPf1qOi7dPq6nPToG2zJOkT+LXhCHMhDnyox5py4gTyj4XzyZFwjwspzW +8PzACquGNKtqS809UXGNACTFl3S6ABBLZ8Rw172VCI1o3ifBKNFRSiMb0SMlS/sE +n2pzFN4CPVs8docE+w2RugqmZxS6v4F8f9BqQBOgsyR3o46d09R4cHrG6Jp2cT4H ++ePqilcUNI2+X8oIekn2eK5i7zgXpgV87dPb5RI7TM+VDmtg7jery/8kxOGBMTOl +Cz3VCa8yVUkJAhe7jOC3OUgka3J2ko5M4PSQVSK1smxktgXc+2dwqr/a+o83aSaa +TOxP9yHpAgMBAAECggEAFJNc8XTBG/MflHvwq3Vn1pEwScwvklDbHj6//h/cuabn +v1MvWCuaapqpcswCtUPaE800lEkE4BsGM7+Z0EQNRmPfE29gp2WMSY2dPqA9T4gi +TIEOjf7ZdZzNsN0EalbuQnZUa2jwzIffXi2VWp3FiKA2rRxDyZ4gGsxj1eikE4wE +1JNbfml6Tcva//aiEMy+W+cguyRTMV/5mHFvaLlzHSi/ZuNj4SMpTNNfXJzlQ7pk +3aX5PjBrQhG6gL2U9b7a98EeTABb8h8ONFX2qw36Skac9p96ZK/cdlX2c4mqTRgl +uBi0l5Hc94NvzEnj8fgU4neZpfU7iTUg2gISommOYQKBgQDmHFn7mYFCa/F83ljl +BriBJzLx99M71zZiaLkYGAt6AHn358VQzEbcIN+BBllS5mJjv5Fkma7mbZeKDfyR +KxXU31GEfCh8AYGC/W15YxNqXrUOClHIg+0O6eZk6wFYqH6803sMMrAE7SuXM1w0 +ZlpNNtJ37b4ymG1+DuW/WKG1cwKBgQDLwTgYQQq64sNjgJJDte5ir7d8TjZX1zcj +dy92cdn3dTqQ2BUjf0VU/GDX39JgTY5AMl4kYa26yLaYnJPtAnA8jcAWQBHIKMvF +3MkOTsCVZDzcbIAPR/UU/nlK7QJUWr3E2xZImYDMPAq0YSK8AOuYsz6vrC9r+Vyq +Jpt+Z2EUMwKBgFpiXH3VB86dM5eGhog/IY2pZfthCpmAqR3yYHG0UB21vjK/2OMp +udORHflCEyfa0l63ylYnf2mHNnTFlaU3tTWOGijd09ERjltzS+LYJbIsTRWcvA70 +stKe0R5mqHq4hD+LGdPqvPTSa8LSK69xXJrjo7vzqDebY7aWB2wnVi09AoGAK0I4 +gJ2+g3MFfKidZRbJJ9aapB+O1hNxN2xkfUcquaj/6CSYSFMLC0IR5YM1jRCqNOL8 +rci3M8LNUZVcqqMr5Q9LSu4LWG2g5b88SHdb19vSOBIpFhV26SAl7ExphDNHuvWw +w3UjrTjKJQXCdBvV6TOVCYMLBmeIzu8ncCzOpZUCgYEAmomOzZz3z9OEN4/i8PTr +81PZSmUJ0TpOYgluwAeSN/CHD1Tr68bftdWKf09XAES5gyUi/T+U2XhPyn43aC+E +d2L8ME0uBlJGFj3ptwcYJK0H0yxTC6BjiakWr2zXyn0NkX/t2NCoUWHZAafIKcjY +Gves4F5qVBAsgiNpTdiTQuw= +-----END PRIVATE KEY----- diff --git a/docs/examples/qdrant/autoscaler/compute/autoscaler.yaml b/docs/examples/qdrant/autoscaler/compute/qdrant-as-compute.yaml similarity index 64% rename from docs/examples/qdrant/autoscaler/compute/autoscaler.yaml rename to docs/examples/qdrant/autoscaler/compute/qdrant-as-compute.yaml index a28811cfd..c8f421f77 100644 --- a/docs/examples/qdrant/autoscaler/compute/autoscaler.yaml +++ b/docs/examples/qdrant/autoscaler/compute/qdrant-as-compute.yaml @@ -12,11 +12,13 @@ spec: compute: node: trigger: "On" + podLifeTimeThreshold: 5m + resourceDiffPercentage: 20 minAllowed: - cpu: 250m - memory: 512Mi + cpu: 400m + memory: 400Mi maxAllowed: - cpu: "2" - memory: 4Gi + cpu: 1 + memory: 2Gi controlledResources: ["cpu", "memory"] - containerControlledValues: RequestsAndLimits \ No newline at end of file + containerControlledValues: "RequestsAndLimits" diff --git a/docs/examples/qdrant/autoscaler/compute/qdrant.yaml b/docs/examples/qdrant/autoscaler/compute/qdrant.yaml new file mode 100644 index 000000000..07ffb37f9 --- /dev/null +++ b/docs/examples/qdrant/autoscaler/compute/qdrant.yaml @@ -0,0 +1,28 @@ +apiVersion: kubedb.com/v1alpha2 +kind: Qdrant +metadata: + name: qdrant-sample + namespace: demo +spec: + version: "1.17.0" + replicas: 3 + storageType: Durable + storage: + storageClassName: "standard" + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 1Gi + podTemplate: + spec: + containers: + - name: qdrant + resources: + requests: + cpu: "200m" + memory: "512Mi" + limits: + cpu: "200m" + memory: "512Mi" + deletionPolicy: WipeOut diff --git a/docs/examples/qdrant/autoscaler/storage/autoscaler.yaml b/docs/examples/qdrant/autoscaler/storage/qdrant-as-storage.yaml similarity index 53% rename from docs/examples/qdrant/autoscaler/storage/autoscaler.yaml rename to docs/examples/qdrant/autoscaler/storage/qdrant-as-storage.yaml index 928672895..d76313abe 100644 --- a/docs/examples/qdrant/autoscaler/storage/autoscaler.yaml +++ b/docs/examples/qdrant/autoscaler/storage/qdrant-as-storage.yaml @@ -6,14 +6,9 @@ metadata: spec: databaseRef: name: qdrant-sample - opsRequestOptions: - timeout: 3m - apply: IfReady storage: node: trigger: "On" - expansionMode: Online - usageThreshold: 70 - scalingThreshold: 50 - minAllowed: 2Gi - maxAllowed: 50Gi \ No newline at end of file + usageThreshold: 20 + scalingThreshold: 20 + expansionMode: "Online" diff --git a/docs/examples/qdrant/autoscaler/storage/qdrant.yaml b/docs/examples/qdrant/autoscaler/storage/qdrant.yaml new file mode 100644 index 000000000..67ca1c8f0 --- /dev/null +++ b/docs/examples/qdrant/autoscaler/storage/qdrant.yaml @@ -0,0 +1,17 @@ +apiVersion: kubedb.com/v1alpha2 +kind: Qdrant +metadata: + name: qdrant-sample + namespace: demo +spec: + version: "1.17.0" + replicas: 3 + storageType: Durable + storage: + storageClassName: "longhorn" + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 1Gi + deletionPolicy: WipeOut diff --git a/docs/examples/qdrant/reconfigure-tls/add-tls.yaml b/docs/examples/qdrant/reconfigure-tls/add-tls.yaml new file mode 100644 index 000000000..dd8ef68d9 --- /dev/null +++ b/docs/examples/qdrant/reconfigure-tls/add-tls.yaml @@ -0,0 +1,25 @@ +apiVersion: ops.kubedb.com/v1alpha1 +kind: QdrantOpsRequest +metadata: + name: qdops-add-tls + namespace: demo +spec: + type: ReconfigureTLS + databaseRef: + name: qdrant-sample + tls: + issuerRef: + name: qdrant-issuer + kind: Issuer + apiGroup: "cert-manager.io" + certificates: + - alias: server + subject: + organizations: + - kubedb:server + dnsNames: + - localhost + ipAddresses: + - "127.0.0.1" + timeout: 5m + apply: IfReady diff --git a/docs/examples/qdrant/reconfigure-tls/issuer.yaml b/docs/examples/qdrant/reconfigure-tls/issuer.yaml new file mode 100644 index 000000000..ca65585cc --- /dev/null +++ b/docs/examples/qdrant/reconfigure-tls/issuer.yaml @@ -0,0 +1,8 @@ +apiVersion: cert-manager.io/v1 +kind: Issuer +metadata: + name: qdrant-issuer + namespace: demo +spec: + ca: + secretName: qdrant-ca diff --git a/docs/examples/qdrant/reconfigure-tls/qdrant.yaml b/docs/examples/qdrant/reconfigure-tls/qdrant.yaml new file mode 100644 index 000000000..549a356d3 --- /dev/null +++ b/docs/examples/qdrant/reconfigure-tls/qdrant.yaml @@ -0,0 +1,15 @@ +apiVersion: kubedb.com/v1alpha2 +kind: Qdrant +metadata: + name: qdrant-sample + namespace: demo +spec: + version: "1.17.0" + replicas: 3 + storage: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 1Gi + deletionPolicy: WipeOut diff --git a/docs/examples/qdrant/reconfigure-tls/remove-tls.yaml b/docs/examples/qdrant/reconfigure-tls/remove-tls.yaml new file mode 100644 index 000000000..362cb8033 --- /dev/null +++ b/docs/examples/qdrant/reconfigure-tls/remove-tls.yaml @@ -0,0 +1,11 @@ +apiVersion: ops.kubedb.com/v1alpha1 +kind: QdrantOpsRequest +metadata: + name: qdops-remove-tls + namespace: demo +spec: + type: ReconfigureTLS + databaseRef: + name: qdrant-sample + tls: + remove: true diff --git a/docs/examples/qdrant/reconfigure-tls/rotate-tls.yaml b/docs/examples/qdrant/reconfigure-tls/rotate-tls.yaml new file mode 100644 index 000000000..b97358b35 --- /dev/null +++ b/docs/examples/qdrant/reconfigure-tls/rotate-tls.yaml @@ -0,0 +1,11 @@ +apiVersion: ops.kubedb.com/v1alpha1 +kind: QdrantOpsRequest +metadata: + name: qdops-rotate-tls + namespace: demo +spec: + type: ReconfigureTLS + databaseRef: + name: qdrant-sample + tls: + rotateCertificates: true diff --git a/docs/examples/qdrant/tls/issuer.yaml b/docs/examples/qdrant/tls/issuer.yaml new file mode 100644 index 000000000..ba8149eea --- /dev/null +++ b/docs/examples/qdrant/tls/issuer.yaml @@ -0,0 +1,8 @@ +apiVersion: cert-manager.io/v1 +kind: Issuer +metadata: + name: qdrant-ca-issuer + namespace: demo +spec: + ca: + secretName: qdrant-ca diff --git a/docs/examples/qdrant/tls/tls-qdrant.yaml b/docs/examples/qdrant/tls/tls-qdrant.yaml new file mode 100644 index 000000000..34b5fbe25 --- /dev/null +++ b/docs/examples/qdrant/tls/tls-qdrant.yaml @@ -0,0 +1,23 @@ +apiVersion: kubedb.com/v1alpha2 +kind: Qdrant +metadata: + name: qdrant-tls + namespace: demo +spec: + version: "1.17.0" + mode: Distributed + replicas: 3 + tls: + issuerRef: + apiGroup: cert-manager.io + name: qdrant-ca-issuer + kind: Issuer + client: true + storage: + storageClassName: "standard" + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 1Gi + deletionPolicy: WipeOut diff --git a/docs/guides/qdrant/autoscaler/compute/compute-autoscale.md b/docs/guides/qdrant/autoscaler/compute/compute-autoscale.md index d4ed02549..c955f0901 100644 --- a/docs/guides/qdrant/autoscaler/compute/compute-autoscale.md +++ b/docs/guides/qdrant/autoscaler/compute/compute-autoscale.md @@ -49,7 +49,7 @@ In this section, we are going to deploy a Qdrant database with version `1.17.0`. apiVersion: kubedb.com/v1alpha2 kind: Qdrant metadata: - name: qdrant-db + name: qdrant-sample namespace: demo spec: version: "1.17.0" @@ -79,22 +79,22 @@ spec: Let's create the `Qdrant` CR we have shown above: ```bash -$ kubectl apply -f https://github.com/kubedb/docs/raw/{{< param "info.version" >}}/docs/examples/qdrant/autoscaler/compute/qdrant-db.yaml -qdrant.kubedb.com/qdrant-db created +$ kubectl apply -f https://github.com/kubedb/docs/raw/{{< param "info.version" >}}/docs/examples/qdrant/autoscaler/compute/qdrant.yaml +qdrant.kubedb.com/qdrant-sample created ``` -Now, wait until `qdrant-db` has status `Ready`: +Now, wait until `qdrant-sample` has status `Ready`: ```bash $ kubectl get qdrant -n demo -NAME VERSION STATUS AGE -qdrant-db 1.17.0 Ready 4m +NAME VERSION STATUS AGE +qdrant-sample 1.17.0 Ready 51s ``` Let's check the Pod container resources: ```bash -$ kubectl get pod -n demo qdrant-db-0 -o json | jq '.spec.containers[].resources' +$ kubectl get pod -n demo qdrant-sample-0 -o json | jq '.spec.containers[].resources' { "limits": { "cpu": "200m", @@ -125,7 +125,7 @@ metadata: namespace: demo spec: databaseRef: - name: qdrant-db + name: qdrant-sample opsRequestOptions: timeout: 3m apply: IfReady @@ -146,7 +146,7 @@ spec: Here, -- `spec.databaseRef.name` specifies that we are performing compute autoscaling on `qdrant-db` database. +- `spec.databaseRef.name` specifies that we are performing compute autoscaling on `qdrant-sample` database. - `spec.compute.node.trigger` specifies that compute resource autoscaling is enabled for the Qdrant nodes. - `spec.compute.node.podLifeTimeThreshold` specifies the minimum age of a Pod before the `VerticalPodAutoscaler` can recommend a resource update. - `spec.compute.node.resourceDiffPercentage` specifies the minimum percentage change needed before applying a new resource recommendation. @@ -171,7 +171,7 @@ Let's check that the `QdrantAutoscaler` resource is created successfully: ```bash $ kubectl get qdrantautoscaler -n demo NAME AGE -qdrant-as-compute 5s +qdrant-as-compute 0s $ kubectl describe qdrantautoscaler qdrant-as-compute -n demo Name: qdrant-as-compute @@ -191,17 +191,21 @@ Spec: Cpu: 1 Memory: 2Gi Min Allowed: - Cpu: 400m - Memory: 400Mi - Pod Life Time Threshold: 10m0s - Resource Diff Percentage: 20 - Trigger: On + Cpu: 400m + Memory: 400Mi + Pod Life Time Threshold: 10m + Resource Diff Percentage: 20 + Trigger: On Database Ref: - Name: qdrant-db + Name: qdrant-sample Ops Request Options: - Apply: IfReady - Timeout: 3m0s -Events: + Apply: IfReady + Max Retries: 1 + Timeout: 3m +Status: + Vpas: + Vpa Name: qdrant-sample +Events: ``` So, the `QdrantAutoscaler` resource is created successfully. The operator will now watch the resource usage of the Qdrant pods and create `QdrantOpsRequest` resources to scale when needed. @@ -210,22 +214,22 @@ After some time, you can observe that the autoscaler has created a `QdrantOpsReq ```bash $ kubectl get qdrantopsrequest -n demo -NAME TYPE STATUS AGE -qdops-qdrant-db-xxxxxxxx VerticalScaling Successful 5m +NAME TYPE STATUS AGE +qdops-qdrant-sample-829lnp VerticalScaling Successful 45s ``` You can then verify the updated resources on the pods: ```bash -$ kubectl get pod -n demo qdrant-db-0 -o json | jq '.spec.containers[].resources' +$ kubectl get pod -n demo qdrant-sample-0 -o json | jq '.spec.containers[].resources' { "limits": { "cpu": "400m", - "memory": "512Mi" + "memory": "400Mi" }, "requests": { "cpu": "400m", - "memory": "512Mi" + "memory": "400Mi" } } ``` @@ -237,7 +241,7 @@ The above output verifies that we have successfully autoscaled the resources of To clean up the Kubernetes resources created by this tutorial, run: ```bash -kubectl delete qdrant -n demo qdrant-db +kubectl delete qdrant -n demo qdrant-sample kubectl delete qdrantautoscaler -n demo qdrant-as-compute kubectl delete ns demo ``` diff --git a/docs/guides/qdrant/autoscaler/storage/storage-autoscale.md b/docs/guides/qdrant/autoscaler/storage/storage-autoscale.md index 9958af7a9..9cfa178e6 100644 --- a/docs/guides/qdrant/autoscaler/storage/storage-autoscale.md +++ b/docs/guides/qdrant/autoscaler/storage/storage-autoscale.md @@ -47,12 +47,13 @@ At first, verify that your cluster has a storage class that supports volume expa ```bash $ kubectl get storageclass -NAME PROVISIONER RECLAIMPOLICY VOLUMEBINDINGMODE ALLOWVOLUMEEXPANSION AGE -standard (default) rancher.io/local-path Delete WaitForFirstConsumer false 79m -topolvm-provisioner topolvm.cybozu.com Delete WaitForFirstConsumer true 78m +NAME PROVISIONER RECLAIMPOLICY VOLUMEBINDINGMODE ALLOWVOLUMEEXPANSION AGE +local-path (default) rancher.io/local-path Delete WaitForFirstConsumer false 28d +longhorn (default) driver.longhorn.io Delete Immediate true 25d +longhorn-static driver.longhorn.io Delete Immediate true 28d ``` -We can see from the output that `topolvm-provisioner` storage class has `ALLOWVOLUMEEXPANSION` set to `true`. We will use it for this tutorial. You can install topolvm from [here](https://github.com/topolvm/topolvm). +We can see from the output that `longhorn` storage class has `ALLOWVOLUMEEXPANSION` set to `true`. We will use it for this tutorial. Now, we are going to deploy a `Qdrant` database using a supported version by `KubeDB` operator. Then we are going to apply `QdrantAutoscaler` to set up autoscaling. @@ -64,14 +65,14 @@ In this section, we are going to deploy a Qdrant database with version `1.17.0`. apiVersion: kubedb.com/v1alpha2 kind: Qdrant metadata: - name: qdrant-db + name: qdrant-sample namespace: demo spec: version: "1.17.0" replicas: 3 storageType: Durable storage: - storageClassName: "topolvm-provisioner" + storageClassName: "longhorn" accessModes: - ReadWriteOnce resources: @@ -83,29 +84,28 @@ spec: Let's create the `Qdrant` CR we have shown above: ```bash -$ kubectl apply -f https://github.com/kubedb/docs/raw/{{< param "info.version" >}}/docs/examples/qdrant/autoscaler/storage/qdrant-db.yaml -qdrant.kubedb.com/qdrant-db created +$ kubectl apply -f https://github.com/kubedb/docs/raw/{{< param "info.version" >}}/docs/examples/qdrant/autoscaler/storage/qdrant.yaml +qdrant.kubedb.com/qdrant-sample created ``` -Now, wait until `qdrant-db` has status `Ready`: +Now, wait until `qdrant-sample` has status `Ready`: ```bash $ kubectl get qdrant -n demo -NAME VERSION STATUS AGE -qdrant-db 1.17.0 Ready 3m46s +NAME VERSION STATUS AGE +qdrant-sample 1.17.0 Ready 101s ``` Let's check the volume size from the Petset and from the persistent volumes: ```bash -$ kubectl get sts -n demo qdrant-db -o json | jq '.spec.volumeClaimTemplates[].spec.resources.requests.storage' +$ kubectl get petset -n demo qdrant-sample -o json | jq '.spec.volumeClaimTemplates[].spec.resources.requests.storage' "1Gi" -$ kubectl get pv -n demo -NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE -pvc-43266d76-f280-4cca-bd78-d13660a84db9 1Gi RWO Delete Bound demo/data-qdrant-db-2 topolvm-provisioner 57s -pvc-4a509b05-774b-42d9-b36d-599c9056af37 1Gi RWO Delete Bound demo/data-qdrant-db-0 topolvm-provisioner 58s -pvc-c27eee12-cd86-4410-b39e-b1dd735fc14d 1Gi RWO Delete Bound demo/data-qdrant-db-1 topolvm-provisioner 57s +$ kubectl get pv -o custom-columns=NAME:.metadata.name,CAPACITY:.spec.capacity.storage,STORAGECLASS:.spec.storageClassName,CLAIM:.spec.claimRef.name | grep qdrant-sample +pvc-31485d1d-5048-4dc2-a2c3-18910b27b661 1Gi longhorn data-qdrant-sample-0 +pvc-683755b9-023d-4d36-8318-8a22d5b79acb 1Gi longhorn data-qdrant-sample-1 +pvc-d494f0aa-41b8-458d-ab56-39946c9b9bfe 1Gi longhorn data-qdrant-sample-2 ``` You can see the Petset has 1GB storage and the capacity of all the persistent volumes is also 1GB. @@ -128,7 +128,7 @@ metadata: namespace: demo spec: databaseRef: - name: qdrant-db + name: qdrant-sample storage: node: trigger: "On" @@ -139,11 +139,11 @@ spec: Here, -- `spec.databaseRef.name` specifies that we are performing storage autoscaling on `qdrant-db` database. +- `spec.databaseRef.name` specifies that we are performing storage autoscaling on `qdrant-sample` database. - `spec.storage.node.trigger` specifies that storage autoscaling is enabled for the Qdrant nodes. - `spec.storage.node.usageThreshold` specifies the storage usage threshold — if storage usage exceeds `20%`, storage autoscaling will be triggered. - `spec.storage.node.scalingThreshold` specifies the scaling threshold — storage will be scaled to `20%` of the current amount. -- `spec.storage.node.expansionMode` specifies the expansion mode of the volume expansion `QdrantOpsRequest` created by `QdrantAutoscaler`. topolvm-provisioner supports online volume expansion so `expansionMode` is set to `Online`. +- `spec.storage.node.expansionMode` specifies the expansion mode of the volume expansion `QdrantOpsRequest` created by `QdrantAutoscaler`. longhorn supports online volume expansion so `expansionMode` is set to `Online`. Let's create the `QdrantAutoscaler` CR we have shown above: @@ -170,7 +170,7 @@ API Version: autoscaling.kubedb.com/v1alpha1 Kind: QdrantAutoscaler Spec: Database Ref: - Name: qdrant-db + Name: qdrant-sample Storage: Node: Expansion Mode: Online @@ -185,46 +185,44 @@ So, the `QdrantAutoscaler` resource is created successfully. The operator will n Now, for this demo, we are going to manually fill up the persistent volume to exceed the `usageThreshold` using the `dd` command to see if storage autoscaling is working: ```bash -$ kubectl exec -it -n demo qdrant-db-0 -- bash -root@qdrant-db-0:/qdrant/storage# df -h /qdrant/storage -Filesystem Size Used Avail Use% Mounted on -/dev/topolvm/57cd4330-784f-42c1-bf8e-e743241df164 1014M 32M 983M 4% /qdrant/storage -root@qdrant-db-0:/qdrant/storage# dd if=/dev/zero of=/qdrant/storage/file.img bs=800M count=1 +$ kubectl exec -n demo qdrant-sample-0 -- df -h /qdrant/storage +Filesystem Size Used Avail Use% Mounted on +/dev/longhorn/pvc-9d79a391-6777-4be5-8f9e-0139d178aada 974M 296K 958M 1% /qdrant/storage + +$ kubectl exec -n demo qdrant-sample-0 -- bash -c "dd if=/dev/zero of=/qdrant/storage/file.img bs=250M count=1 && df -h /qdrant/storage" 1+0 records in 1+0 records out -838860800 bytes (839 MB, 800 MiB) copied, 6.47 s, 130 MB/s -root@qdrant-db-0:/qdrant/storage# df -h /qdrant/storage -Filesystem Size Used Avail Use% Mounted on -/dev/topolvm/57cd4330-784f-42c1-bf8e-e743241df164 1014M 832M 183M 82% /qdrant/storage +262144000 bytes (262 MB, 250 MiB) copied, 2.01673 s, 130 MB/s +Filesystem Size Used Avail Use% Mounted on +/dev/longhorn/pvc-9d79a391-6777-4be5-8f9e-0139d178aada 974M 251M 708M 27% /qdrant/storage ``` Now let's watch the `QdrantOpsRequest` in the demo namespace: ```bash $ kubectl get qdrantopsrequest -n demo -w -NAME TYPE STATUS AGE -qdops-qdrant-db-xxxxxxxx VolumeExpansion Progressing 10s -qdops-qdrant-db-xxxxxxxx VolumeExpansion Successful 2m +NAME TYPE STATUS AGE +qdops-qdrant-sample-ka4wgv VolumeExpansion Progressing 2s +qdops-qdrant-sample-ka4wgv VolumeExpansion Successful 8m ``` After the `QdrantOpsRequest` completes successfully, let's check the updated storage: ```bash -$ kubectl get pv -n demo -NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE -pvc-43266d76-f280-4cca-bd78-d13660a84db9 1217Mi RWO Delete Bound demo/data-qdrant-db-2 topolvm-provisioner 15m -pvc-4a509b05-774b-42d9-b36d-599c9056af37 1217Mi RWO Delete Bound demo/data-qdrant-db-0 topolvm-provisioner 15m -pvc-c27eee12-cd86-4410-b39e-b1dd735fc14d 1217Mi RWO Delete Bound demo/data-qdrant-db-1 topolvm-provisioner 15m +$ kubectl get pv -o custom-columns=NAME:.metadata.name,CAPACITY:.spec.capacity.storage,STORAGECLASS:.spec.storageClassName,CLAIM:.spec.claimRef.name | grep qdrant-sample +pvc-31485d1d-5048-4dc2-a2c3-18910b27b661 1168Mi longhorn data-qdrant-sample-0 +pvc-683755b9-023d-4d36-8318-8a22d5b79acb 1168Mi longhorn data-qdrant-sample-1 +pvc-d494f0aa-41b8-458d-ab56-39946c9b9bfe 1168Mi longhorn data-qdrant-sample-2 ``` -The storage has been automatically scaled from 1Gi to ~1.2Gi (120% of 1Gi) as we specified a `scalingThreshold` of 20%. +The storage has been automatically scaled from 1Gi to ~1168Mi as we specified a `scalingThreshold` of 20%. ## Cleaning Up To clean up the Kubernetes resources created by this tutorial, run: ```bash -kubectl delete qdrant -n demo qdrant-db +kubectl delete qdrant -n demo qdrant-sample kubectl delete qdrantautoscaler -n demo qdrant-as-storage kubectl delete ns demo ``` diff --git a/docs/guides/qdrant/concepts/_index.md b/docs/guides/qdrant/concepts/_index.md index 1701e7aa2..589884fba 100644 --- a/docs/guides/qdrant/concepts/_index.md +++ b/docs/guides/qdrant/concepts/_index.md @@ -8,156 +8,3 @@ menu: weight: 20 menu_name: docs_{{ .version }} --- - -> New to KubeDB? Please start [here](/docs/README.md). - -# Qdrant - -## What is Qdrant - -`Qdrant` is a Kubernetes `Custom Resource Definitions` (CRD). It provides declarative configuration for [Qdrant](https://qdrant.tech/) vector databases in a Kubernetes native way. You only need to describe the desired database configuration in a `Qdrant` object, and the KubeDB operator will create Kubernetes objects in the desired state for you. - -## Qdrant Spec - -As with all other Kubernetes objects, a `Qdrant` CR needs `apiVersion`, `kind`, and `metadata` fields. It also needs a `.spec` section. - -Below is an example `Qdrant` object: - -```yaml -apiVersion: kubedb.com/v1alpha2 -kind: Qdrant -metadata: - name: qdrant-sample - namespace: demo -spec: - version: "1.17.0" - replicas: 3 - authSecret: - name: qdrant-sample-auth - configSecret: - name: qdrant-config - storageType: Durable - storage: - storageClassName: standard - accessModes: - - ReadWriteOnce - resources: - requests: - storage: 1Gi - tls: - issuerRef: - apiGroup: "cert-manager.io" - kind: Issuer - name: qdrant-issuer - monitor: - agent: prometheus.io/operator - prometheus: - serviceMonitor: - labels: - app: kubedb - interval: 10s - podTemplate: - metadata: - annotations: - passMe: ToDatabasePod - spec: - serviceAccountName: my-custom-sa - nodeSelector: - disktype: ssd - imagePullSecrets: - - name: myregistrykey - containers: - - name: qdrant - resources: - requests: - memory: "512Mi" - cpu: "500m" - limits: - memory: "1Gi" - cpu: "1" - serviceTemplates: - - alias: primary - spec: - type: LoadBalancer - deletionPolicy: Halt -``` - -### spec.version - -`spec.version` is a required field that specifies the name of the [QdrantVersion](/docs/guides/qdrant/concepts/catalog.md) CRD where the docker images are specified. Currently, when you install KubeDB, it creates the following `QdrantVersion` CRDs: - -```bash -$ kubectl get qdrantversions -NAME VERSION DB_IMAGE DEPRECATED AGE -1.7.4 1.7.4 qdrant/qdrant:v1.7.4 3d -1.10.0 1.10.0 qdrant/qdrant:v1.10.0 3d -1.14.0 1.14.0 qdrant/qdrant:v1.14.0 3d -1.17.0 1.17.0 qdrant/qdrant:v1.17.0 3d -``` - -### spec.replicas - -`spec.replicas` is an optional field that specifies the number of Qdrant pods to run. For a single-node deployment, set it to `1`. For a multi-node cluster, set it to the desired number of replicas (e.g., `3`). - -### spec.authSecret - -`spec.authSecret` is an optional field that points to a Secret used for Qdrant API key authentication. If not provided, KubeDB will create one automatically. The Secret must contain an `api-key` data field. - -### spec.configSecret - -`spec.configSecret` is an optional field that points to a Secret containing a custom `production.yaml` configuration file for Qdrant. See [Custom Configuration](/docs/guides/qdrant/configuration/using-config-file.md) for details. - -### spec.storageType - -`spec.storageType` specifies the type of storage that will be used for Qdrant. It can be `Durable` or `Ephemeral`. The default value is `Durable`. If `Ephemeral` is used, KubeDB will create Qdrant using `EmptyDir` volume. In this case, you don't have to specify `spec.storage`. This is useful for testing purposes. - -### spec.storage - -`spec.storage` specifies the StorageClass of PVCs that will be dynamically allocated to store data for Qdrant pods. If `spec.storageType: Ephemeral` is not set, this field is required. - -### spec.tls - -`spec.tls` specifies TLS/SSL configurations for Qdrant. It contains the following sub-fields: - -- `spec.tls.issuerRef` points to a cert-manager `Issuer` or `ClusterIssuer` used to issue certificates. -- `spec.tls.certificates` is an optional field that lists additional certificates for the Qdrant server. - -### spec.monitor - -`spec.monitor` specifies the monitoring configuration for Qdrant. It contains the following sub-field: - -- `spec.monitor.agent` specifies the monitoring agent. Valid values are `prometheus.io/builtin` and `prometheus.io/operator`. - -### spec.podTemplate - -KubeDB allows providing a template for database pods through `spec.podTemplate`. KubeDB operator will pass the information provided in `spec.podTemplate` to the Petset created for Qdrant database. Notable sub-fields include: - -- `spec.podTemplate.spec.serviceAccountName` to provide a custom ServiceAccount. -- `spec.podTemplate.spec.imagePullSecrets` to pull images from a private registry. -- `spec.podTemplate.spec.nodeSelector` to schedule pods on specific nodes. -- `spec.podTemplate.spec.containers[].resources` to configure CPU and memory resources. - -### spec.serviceTemplates - -`spec.serviceTemplates` is an optional field that contains a list of the service templates for the Qdrant services. KubeDB allows following service template variables: - -- `spec.serviceTemplates[].alias`: specifies which service template (e.g., `primary`). -- `spec.serviceTemplates[].spec.type`: specifies the service type (e.g., `ClusterIP`, `LoadBalancer`, `NodePort`). - -### spec.deletionPolicy - -`spec.deletionPolicy` gives freedom to the user to control the behavior of KubeDB when a Qdrant object is deleted. Possible values are: - -- `DoNotTerminate`: prevents deletion of the object if admission webhook is enabled. -- `Halt`: deletes the Qdrant object but keeps the underlying resources (PVCs, Secrets) intact. -- `Delete`: deletes the Qdrant object and its PVCs, but not Secrets. -- `WipeOut`: deletes the Qdrant object and all related resources including PVCs and Secrets. - -### spec.disableSecurity - -`spec.disableSecurity` is an optional boolean field that disables API key authentication when set to `true`. The default is `false`. - -## Next Steps - -- Learn about [QdrantVersion CRD](/docs/guides/qdrant/concepts/catalog.md). -- Deploy your first Qdrant database with KubeDB by following the guide [here](/docs/guides/qdrant/quickstart/quickstart.md). \ No newline at end of file diff --git a/docs/guides/qdrant/concepts/autoscaler.md b/docs/guides/qdrant/concepts/autoscaler.md index 4abc4e1bb..c29a3c9b3 100644 --- a/docs/guides/qdrant/concepts/autoscaler.md +++ b/docs/guides/qdrant/concepts/autoscaler.md @@ -4,7 +4,7 @@ menu: docs_{{ .version }}: identifier: qdrant-autoscaler-concepts name: QdrantAutoscaler - parent: qdrant-concepts-qdrant + parent: qdrant-concepts weight: 30 menu_name: docs_{{ .version }} section_menu_id: guides @@ -82,15 +82,23 @@ A `QdrantAutoscaler` object has the following fields in the `spec` section: #### spec.compute -`spec.compute` specifies the compute (CPU and memory) autoscaling configuration. It contains a `node` sub-section with the following fields: - -- `spec.compute.node.trigger` — enables (`On`) or disables (`Off`) compute autoscaling. -- `spec.compute.node.podLifeTimeThreshold` — the minimum age of a pod before VPA can recommend resource updates. -- `spec.compute.node.resourceDiffPercentage` — the minimum percentage difference required before applying a recommendation. -- `spec.compute.node.minAllowed` — the minimum allowed CPU and memory resources. -- `spec.compute.node.maxAllowed` — the maximum allowed CPU and memory resources. -- `spec.compute.node.controlledResources` — the list of resources to be controlled (e.g., `["cpu", "memory"]`). -- `spec.compute.node.containerControlledValues` — specifies whether to control `RequestsAndLimits` or `RequestsOnly`. +`spec.compute` specifies the compute (CPU and memory) autoscaling configuration. It contains: + +- `spec.compute.node` — the per-node compute autoscaling configuration: + - `trigger` — enables (`On`) or disables (`Off`) compute autoscaling. + - `podLifeTimeThreshold` — the minimum age of a pod before VPA can recommend resource updates. + - `resourceDiffPercentage` — the minimum percentage difference required before applying a recommendation. + - `minAllowed` — the minimum allowed CPU and memory resources. + - `maxAllowed` — the maximum allowed CPU and memory resources. + - `controlledResources` — the list of resources to be controlled (e.g., `["cpu", "memory"]`). + - `containerControlledValues` — specifies whether to control `RequestsAndLimits` or `RequestsOnly`. + - `inMemoryStorage` — configuration for in-memory storage autoscaling per node: + - `scalingFactorPercentage` — the scaling factor percentage for in-memory storage. + - `usageThresholdPercentage` — the usage threshold percentage that triggers scaling. +- `spec.compute.nodeTopology` — specifies per-node topology for compute autoscaling: + - `name` — the name of the topology entry. + - `scaleDownDiffPercentage` — the scale-down difference percentage for this topology. + - `scaleUpDiffPercentage` — the scale-up difference percentage for this topology. #### spec.storage @@ -100,6 +108,10 @@ A `QdrantAutoscaler` object has the following fields in the `spec` section: - `spec.storage.node.usageThreshold` — the storage usage threshold (percentage) that triggers autoscaling. - `spec.storage.node.scalingThreshold` — the percentage by which storage will be scaled when triggered. - `spec.storage.node.expansionMode` — the volume expansion mode (`Online` or `Offline`). +- `spec.storage.node.upperBound` — the upper bound for storage size. +- `spec.storage.node.scalingRules` — a list of scaling rule objects, each containing: + - `appliesUpto` — the upper limit for which this rule applies. + - `threshold` — the threshold for this scaling rule. #### spec.opsRequestOptions @@ -107,6 +119,7 @@ A `QdrantAutoscaler` object has the following fields in the `spec` section: - `spec.opsRequestOptions.timeout` — the timeout for the generated ops request. - `spec.opsRequestOptions.apply` — when to apply the ops request. Can be `Always` or `IfReady`. +- `spec.opsRequestOptions.maxRetries` — the maximum number of retries for the ops request. ## Next Steps diff --git a/docs/guides/qdrant/concepts/catalog.md b/docs/guides/qdrant/concepts/catalog.md index c2cb65340..f7fc209e3 100644 --- a/docs/guides/qdrant/concepts/catalog.md +++ b/docs/guides/qdrant/concepts/catalog.md @@ -4,7 +4,7 @@ menu: docs_{{ .version }}: identifier: qdrant-catalog-concepts name: QdrantVersion - parent: qdrant-concepts-qdrant + parent: qdrant-concepts weight: 15 menu_name: docs_{{ .version }} section_menu_id: guides @@ -60,13 +60,38 @@ The default value of this field is `false`. If `spec.deprecated` is set to `true ```bash $ kubectl get qdrantversions -NAME VERSION DB_IMAGE DEPRECATED AGE -1.7.4 1.7.4 qdrant/qdrant:v1.7.4 3d -1.10.0 1.10.0 qdrant/qdrant:v1.10.0 3d -1.14.0 1.14.0 qdrant/qdrant:v1.14.0 3d -1.17.0 1.17.0 qdrant/qdrant:v1.17.0 3d +NAME VERSION DB_IMAGE DEPRECATED AGE +1.15.4 1.15.4 docker.io/qdrant/qdrant:v1.15.4-unprivileged 28d +1.16.2 1.16.2 docker.io/qdrant/qdrant:v1.16.2-unprivileged 28d +1.17.0 1.17.0 docker.io/qdrant/qdrant:v1.17.0-unprivileged 28d ``` +### spec.endOfLife + +`spec.endOfLife` is an optional `` field that indicates whether this Qdrant version has reached its end of life. + +### spec.securityContext + +`spec.securityContext` specifies the security context for the database container. It contains: + +- `spec.securityContext.runAsUser` — the user ID to run the container as. + +### spec.ui + +`spec.ui` is an optional array that specifies the UI configuration for this Qdrant version. Each entry contains: + +- `name` — the name of the UI component. +- `version` — the version of the UI component. +- `values` — configuration values for the UI component. +- `disable` — whether the UI component is disabled. + +### spec.updateConstraints + +`spec.updateConstraints` specifies constraints for version updates. It contains: + +- `spec.updateConstraints.allowlist` — a list of versions that are allowed as update targets from this version. +- `spec.updateConstraints.denylist` — a list of versions that are forbidden as update targets from this version. + ## Next Steps - Learn about the [Qdrant CRD](/docs/guides/qdrant/concepts/qdrant.md). diff --git a/docs/guides/qdrant/concepts/opsrequest.md b/docs/guides/qdrant/concepts/opsrequest.md index 18bef6587..4a7ab819a 100644 --- a/docs/guides/qdrant/concepts/opsrequest.md +++ b/docs/guides/qdrant/concepts/opsrequest.md @@ -4,7 +4,7 @@ menu: docs_{{ .version }}: identifier: qdrant-opsrequest-concepts name: QdrantOpsRequest - parent: qdrant-concepts-qdrant + parent: qdrant-concepts weight: 25 menu_name: docs_{{ .version }} section_menu_id: guides @@ -126,6 +126,30 @@ A `QdrantOpsRequest` object has the following fields in the `spec` section: - `VerticalScaling` — vertically scale the resources (CPU and memory) of database pods. - `VolumeExpansion` — expand the persistent volume claim size of a running Qdrant database. +#### spec.authentication + +`spec.authentication` is used when `spec.type` is `RotateAuth`. It contains: + +- `spec.authentication.secretRef` — a reference to the secret containing the new authentication credentials: + - `apiGroup` — the API group of the referenced secret. + - `kind` — the kind of the referenced secret. + - `name` — the name of the secret (required). + +#### spec.maxRetries + +`spec.maxRetries` is an optional `` field that specifies the maximum number of times the ops request should be retried if it fails. + +#### spec.migration + +`spec.migration` is used when `spec.type` is `VolumeExpansion` or other migration-requiring operations. It contains: + +- `spec.migration.storageClassName` — the target storage class name for migration. +- `spec.migration.oldPVReclaimPolicy` — the reclaim policy for the old PersistentVolume. + +#### spec.restart + +`spec.restart` is used when `spec.type` is `Restart`. It is an empty object (`{}`). No further configuration is needed for a restart operation. + #### spec.updateVersion `spec.updateVersion` is used when `spec.type` is `UpdateVersion`. It contains: @@ -142,15 +166,38 @@ A `QdrantOpsRequest` object has the following fields in the `spec` section: `spec.verticalScaling` is used when `spec.type` is `VerticalScaling`. It contains: -- `spec.verticalScaling.node.resources` — the CPU and memory resource requests and limits for Qdrant nodes. +- `spec.verticalScaling.node` — the per-node vertical scaling configuration: + - `resources` — the CPU and memory resource requests and limits for Qdrant nodes. + - `nodeSelectionPolicy` — the policy for selecting nodes to scale. + - `topology` — the topology constraints for the vertical scaling operation: + - `key` — the topology key (required). + - `value` — the topology value (required). #### spec.volumeExpansion `spec.volumeExpansion` is used when `spec.type` is `VolumeExpansion`. It contains: -- `spec.volumeExpansion.node` — the new desired storage size for Qdrant nodes. +- `spec.volumeExpansion.node` — the per-node volume expansion configuration. Can be an empty object `{}` if the volume expansion should use defaults. - `spec.volumeExpansion.mode` — the volume expansion mode. Can be `Online` or `Offline`. +#### spec.tls + +`spec.tls` is used when `spec.type` is `ReconfigureTLS`. It contains: + +- `spec.tls.client` — TLS configuration for client connections. +- `spec.tls.p2p` — TLS configuration for peer-to-peer connections. +- `spec.tls.remove` — specifies whether to remove TLS configuration. +- `spec.tls.rotateCertificates` — specifies whether to rotate TLS certificates. + +#### spec.configuration + +`spec.configuration` is used when `spec.type` is `Reconfigure`. It contains: + +- `spec.configuration.applyConfig` — a map of key-value pairs for inline configuration changes. +- `spec.configuration.configSecret` — the secret containing the new configuration. +- `spec.configuration.removeCustomConfig` — specifies whether to remove the custom configuration. +- `spec.configuration.restart` — specifies the restart behavior after applying configuration. Supported values are `auto`, `true`, and `false`. + #### spec.timeout `spec.timeout` is an optional field that specifies the timeout duration for the OpsRequest to complete. If the OpsRequest does not complete within the specified timeout, it will be marked as failed. The value is in the form of a Kubernetes duration (e.g., `5m`, `1h`). @@ -167,16 +214,27 @@ A `QdrantOpsRequest` object has the following fields in the `spec` section: `status.phase` indicates the overall phase of the operation for this `QdrantOpsRequest`. It can have the following values: -| Phase | Meaning | -|-------------|---------------------------------------------------------------------------------| -| Successful | KubeDB has successfully performed the operation requested in the QdrantOpsRequest | -| Progressing | KubeDB has started the execution of the applied QdrantOpsRequest | -| Failed | KubeDB has failed the operation requested in the QdrantOpsRequest | -| Denied | KubeDB has denied the operation requested in the QdrantOpsRequest | -| Skipped | KubeDB has skipped the operation requested in the QdrantOpsRequest | +| Phase | Meaning | +|--------------------|---------------------------------------------------------------------------------| +| Pending | The QdrantOpsRequest has been created but execution has not started yet | +| Progressing | KubeDB has started the execution of the applied QdrantOpsRequest | +| Successful | KubeDB has successfully performed the operation requested in the QdrantOpsRequest | +| Failed | KubeDB has failed the operation requested in the QdrantOpsRequest | +| Denied | KubeDB has denied the operation requested in the QdrantOpsRequest | +| Skipped | KubeDB has skipped the operation requested in the QdrantOpsRequest | +| WaitingForApproval | The QdrantOpsRequest is waiting for approval before execution | Ops-manager Operator can skip an opsRequest only if its execution has not been started yet and there is a newer opsRequest applied in the cluster. `spec.type` has to be the same as the skipped one, in this case. +#### status.pausedBackups + +`status.pausedBackups` is a list of references to backup objects that were paused during the operation. Each entry has: + +- `apiGroup` — the API group of the paused backup. +- `kind` — the kind of the paused backup. +- `name` — the name of the paused backup (required). +- `namespace` — the namespace of the paused backup. + #### status.observedGeneration `status.observedGeneration` shows the most recent generation observed by the `QdrantOpsRequest` controller. diff --git a/docs/guides/qdrant/concepts/qdrant.md b/docs/guides/qdrant/concepts/qdrant.md new file mode 100644 index 000000000..f01e39773 --- /dev/null +++ b/docs/guides/qdrant/concepts/qdrant.md @@ -0,0 +1,236 @@ +--- +title: Qdrant CRD +menu: + docs_{{ .version }}: + identifier: qdrant-concepts-qdrant + name: Qdrant + parent: qdrant-concepts + weight: 10 +menu_name: docs_{{ .version }} +--- + +> New to KubeDB? Please start [here](/docs/README.md). + +# Qdrant + +## What is Qdrant + +`Qdrant` is a Kubernetes `Custom Resource Definitions` (CRD). It provides declarative configuration for [Qdrant](https://qdrant.tech/) vector databases in a Kubernetes native way. You only need to describe the desired database configuration in a `Qdrant` object, and the KubeDB operator will create Kubernetes objects in the desired state for you. + +## Qdrant Spec + +As with all other Kubernetes objects, a `Qdrant` CR needs `apiVersion`, `kind`, and `metadata` fields. It also needs a `.spec` section. + +Below is an example `Qdrant` object: + +```yaml +apiVersion: kubedb.com/v1alpha2 +kind: Qdrant +metadata: + name: qdrant-sample + namespace: demo +spec: + version: "1.17.0" + replicas: 3 + authSecret: + name: qdrant-sample-auth + configSecret: + name: qdrant-config + storageType: Durable + storage: + storageClassName: standard + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 1Gi + tls: + issuerRef: + apiGroup: "cert-manager.io" + kind: Issuer + name: qdrant-issuer + monitor: + agent: prometheus.io/operator + prometheus: + serviceMonitor: + labels: + app: kubedb + interval: 10s + podTemplate: + metadata: + annotations: + passMe: ToDatabasePod + spec: + serviceAccountName: my-custom-sa + nodeSelector: + disktype: ssd + imagePullSecrets: + - name: myregistrykey + containers: + - name: qdrant + resources: + requests: + memory: "512Mi" + cpu: "500m" + limits: + memory: "1Gi" + cpu: "1" + serviceTemplates: + - alias: primary + spec: + type: LoadBalancer + deletionPolicy: Halt +``` + +### spec.version + +`spec.version` (required) specifies the name of the [QdrantVersion](/docs/guides/qdrant/concepts/catalog.md) CRD where the docker images are specified. + +```bash +$ kubectl get qdrantversions +NAME VERSION DB_IMAGE DEPRECATED AGE +1.15.4 1.15.4 docker.io/qdrant/qdrant:v1.15.4-unprivileged 28d +1.16.2 1.16.2 docker.io/qdrant/qdrant:v1.16.2-unprivileged 28d +1.17.0 1.17.0 docker.io/qdrant/qdrant:v1.17.0-unprivileged 28d +``` + +### spec.replicas + +`spec.replicas` is an optional `` field that specifies the number of Qdrant pods to run. For a single-node deployment, set it to `1`. For a multi-node distributed cluster, set it to the desired number of replicas (e.g., `3`). + +### spec.mode + +`spec.mode` specifies the deployment mode for the Qdrant cluster. Supported values are: + +- `Standalone` — runs a single Qdrant node. +- `Distributed` — runs a multi-node Qdrant cluster with peer-to-peer communication. Required for `spec.tls.p2p`. + +### spec.authSecret + +`spec.authSecret` is an optional field that points to a Secret used for Qdrant API key authentication. If not provided, KubeDB will create one automatically. It contains the following sub-fields: + +- `name` — the name of the Secret (required). +- `kind` — the kind of the secret reference (required). +- `apiGroup` — the API group of the secret reference. +- `externallyManaged` — specifies whether the secret is managed externally. +- `activeFrom` — the time from which the secret becomes active. +- `rotateAfter` — the duration after which the secret should be rotated. +- `secretStoreName` — the name of the external secret store. + +### spec.configSecret + +`spec.configSecret` is an optional field that points to a Secret containing a custom `production.yaml` configuration file for Qdrant. See [Custom Configuration](/docs/guides/qdrant/configuration/using-config-file.md) for details. + +### spec.configuration + +`spec.configuration` is an optional field for providing custom Qdrant configuration. It has the following sub-fields: + +- `inline` — a map of key-value pairs for inline configuration. +- `secretName` — the name of a Secret containing the configuration. + +### spec.storageType + +`spec.storageType` specifies the type of storage that will be used for Qdrant. Supported values are: + +- `Durable` — uses PersistentVolumeClaims (default). +- `Ephemeral` — uses `EmptyDir` volumes (useful for testing). + +### spec.storage + +`spec.storage` specifies the PersistentVolumeClaim configuration for Qdrant data. It contains standard PVC fields like `storageClassName`, `accessModes`, `resources`, `selector`, etc. + +### spec.healthChecker + +`spec.healthChecker` specifies the configuration for database health checking. It contains the following sub-fields: + +- `disableWriteCheck` — disables write health checks. +- `failureThreshold` — the number of consecutive failures before marking the database as unhealthy. +- `periodSeconds` — the interval between health checks. +- `timeoutSeconds` — the timeout for each health check. + +### spec.halted + +`spec.halted` is an optional `` field. When set to `true`, the database will be halted (all pods stopped) while preserving the PVCs and other resources. + +### spec.disableSecurity + +`spec.disableSecurity` is an optional `` field that disables API key authentication when set to `true`. The default is `false`. + +### spec.tls + +`spec.tls` specifies the TLS/SSL configurations for the Qdrant database. KubeDB uses [cert-manager](https://cert-manager.io/) v1 api to provision and manage TLS certificates. + +The following fields are configurable in the `spec.tls` section: + +- `issuerRef` is a reference to the `Issuer` or `ClusterIssuer` CR of [cert-manager](https://cert-manager.io/docs/concepts/issuer/) that will be used by `KubeDB` to generate necessary certificates. + + - `apiGroup` is the group name of the resource that is being referenced. Currently, the only supported value is `cert-manager.io`. + - `kind` — the type of resource. KubeDB supports both `Issuer` and `ClusterIssuer`. + - `name` — the name of the resource being referenced (required). + +- `client` (optional, ``, default `false`) enables TLS for client-to-server communication. When set to `true`, the Qdrant server will accept TLS-encrypted connections from clients. + +- `p2p` (optional, ``, default `false`) enables TLS for peer-to-peer communication between Qdrant nodes. When set to `true`, inter-node communication within the Qdrant cluster will be encrypted using TLS. Requires `spec.mode` to be `Distributed`. + +- `certificates` (optional) is a list of additional certificates used to configure the Qdrant server. Each certificate has the following fields: + - `alias` — the identifier of the certificate (required). The supported value is `server`. + - `secretName` (optional) specifies the Kubernetes secret name that holds the certificates. If not specified, defaults to `--cert`. + - `issuerRef` (optional) specifies a separate issuer for this certificate. If not set, the top-level `issuerRef` is used. + - `apiGroup` — the API group of the issuer. + - `kind` — the kind of the issuer (`Issuer` or `ClusterIssuer`). + - `name` — the name of the issuer. + - `subject` (optional) specifies an `X.509` distinguished name with the following sub-fields: + - `organizations` — list of organization names. + - `organizationalUnits` — list of organization unit names. + - `countries` — list of country names. + - `localities` — list of locality names. + - `provinces` — list of province names. + - `streetAddresses` — list of street addresses. + - `postalCodes` — list of postal codes. + - `serialNumber` — serial number. + - `duration` (optional) — the validity period of the certificate. + - `renewBefore` (optional) — the time before expiration to renew the certificate. + - `dnsNames` (optional) — list of DNS subject alternative names. + - `ipAddresses` (optional) — list of IP subject alternative names. + - `uris` (optional) — list of URI Subject Alternative Names. + - `emailAddresses` (optional) — list of email Subject Alternative Names. + - `privateKey` (optional) specifies options for the private key: + - `encoding` — the private key encoding. Supported values are `PKCS1` and `PKCS8`. + +### spec.monitor + +`spec.monitor` specifies the monitoring configuration for Qdrant. It contains: + +- `agent` — the monitoring agent. Supported values are `prometheus.io/builtin`, `prometheus.io`, and `prometheus.io/operator`. +- `prometheus` — Prometheus-specific configuration including `exporter` and `serviceMonitor` settings. + +### spec.podTemplate + +KubeDB allows providing a template for database pods through `spec.podTemplate`. KubeDB operator will pass the information provided in `spec.podTemplate` to the PetSet created for Qdrant database. Notable sub-fields include: + +- `spec.podTemplate.spec.serviceAccountName` — provide a custom ServiceAccount. +- `spec.podTemplate.spec.imagePullSecrets` — pull images from a private registry. +- `spec.podTemplate.spec.nodeSelector` — schedule pods on specific nodes. +- `spec.podTemplate.spec.containers[].resources` — configure CPU and memory resources. +- `spec.podTemplate.spec.containers[].env` — set environment variables for the container. + +### spec.serviceTemplates + +`spec.serviceTemplates` is an optional list of service templates. Each entry has: + +- `alias` — the service alias (required). Supported values include `primary`, `standby`, `stats`, `dashboard`, etc. +- `spec` — the service specification including `type`, `clusterIP`, `ports`, etc. + +### spec.deletionPolicy + +`spec.deletionPolicy` controls the behavior when a Qdrant object is deleted. Supported values are: + +- `Halt` — deletes the Qdrant object but keeps underlying resources (PVCs, Secrets). +- `Delete` — deletes the Qdrant object and its PVCs, but not Secrets. +- `WipeOut` — deletes the Qdrant object and all related resources including PVCs and Secrets. +- `DoNotTerminate` — prevents deletion if admission webhook is enabled. + +## Next Steps + +- Learn about [QdrantVersion CRD](/docs/guides/qdrant/concepts/catalog.md). +- Deploy your first Qdrant database with KubeDB by following the guide [here](/docs/guides/qdrant/quickstart/quickstart.md). diff --git a/docs/guides/qdrant/images/qdrant-tls.png b/docs/guides/qdrant/images/qdrant-tls.png new file mode 100644 index 0000000000000000000000000000000000000000..f3e8950577934dba60cdeda38e4dade70f78e3e9 GIT binary patch literal 57000 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 +Reconfigure Qdrant TLS +
Fig: Reconfigure Qdrant TLS
+ + The Reconfigure TLS process consists of the following steps: 1. At first, a user creates a `Qdrant` CR. 2. `KubeDB-Provisioner` operator watches the `Qdrant` CR. -3. When the operator finds a `Qdrant` CR, it creates a `Petset` and related necessary stuff like secrets, services, etc. +3. When the operator finds a `Qdrant` CR, it creates a `PetSet` and related necessary resources like secrets, services, etc. -4. Then, in order to reconfigure TLS of the `Qdrant` database, the user creates a `QdrantOpsRequest` CR specifying the desired TLS configuration. The user can add TLS to an existing non-TLS database, rotate the existing certificates, or remove TLS entirely. +4. Then, in order to reconfigure TLS of the `Qdrant` database, the user creates a `QdrantOpsRequest` CR specifying the desired TLS configuration. The user can add TLS to an existing non-TLS database, rotate the existing certificates, change the issuer, or remove TLS entirely. 5. `KubeDB` Ops-manager operator watches the `QdrantOpsRequest` CR. @@ -56,4 +80,10 @@ The Reconfigure TLS process consists of the following steps: 9. After the successful Reconfigure TLS, the `KubeDB` Ops-manager resumes the `Qdrant` object so that the `KubeDB-Provisioner` resumes its usual operations. +KubeDB supports the following TLS reconfiguration operations for Qdrant: + +- **Add TLS** — Enable TLS on an existing non-TLS Qdrant database. +- **Rotate TLS** — Rotate the existing TLS certificates to refresh expiring certificates. +- **Remove TLS** — Remove TLS from an existing TLS-enabled Qdrant database. + In the next doc, we are going to show a step-by-step guide on reconfiguring TLS for a Qdrant database using `QdrantOpsRequest` CRD. diff --git a/docs/guides/qdrant/reconfigure-tls/reconfigure-tls.md b/docs/guides/qdrant/reconfigure-tls/reconfigure-tls.md index 517b5b95e..ba83becad 100644 --- a/docs/guides/qdrant/reconfigure-tls/reconfigure-tls.md +++ b/docs/guides/qdrant/reconfigure-tls/reconfigure-tls.md @@ -36,7 +36,7 @@ $ kubectl create ns demo namespace/demo created ``` -> **Note:** YAML files used in this tutorial are stored in [docs/guides/qdrant/reconfigure-tls/yamls](/docs/guides/qdrant/reconfigure-tls/yamls) directory of [kubedb/docs](https://github.com/kubedb/docs) repository. +> **Note:** YAML files used in this tutorial are stored in [docs/examples/qdrant/reconfigure-tls](/docs/examples/qdrant/reconfigure-tls) directory of [kubedb/docs](https://github.com/kubedb/docs) repository. ## Add TLS to a Qdrant database @@ -67,7 +67,7 @@ spec: Let's create the `Qdrant` CR we have shown above: ```bash -$ kubectl create -f https://github.com/kubedb/docs/raw/{{< param "info.version" >}}/docs/guides/qdrant/reconfigure-tls/yamls/qdrant.yaml +$ kubectl create -f https://github.com/kubedb/docs/raw/{{< param "info.version" >}}/docs/examples/qdrant/reconfigure-tls/qdrant.yaml qdrant.kubedb.com/qdrant-sample created ``` @@ -114,7 +114,7 @@ spec: ``` ```bash -$ kubectl apply -f https://github.com/kubedb/docs/raw/{{< param "info.version" >}}/docs/guides/qdrant/reconfigure-tls/yamls/issuer.yaml +$ kubectl apply -f https://github.com/kubedb/docs/raw/{{< param "info.version" >}}/docs/examples/qdrant/reconfigure-tls/issuer.yaml issuer.cert-manager.io/qdrant-issuer created ``` @@ -160,7 +160,7 @@ Here, - `spec.apply` specifies when to apply the operation (learn more [here](/docs/guides/qdrant/concepts/opsrequest.md#specapply)). ```bash -$ kubectl apply -f https://github.com/kubedb/docs/raw/{{< param "info.version" >}}/docs/guides/qdrant/reconfigure-tls/yamls/add-tls.yaml +$ kubectl apply -f https://github.com/kubedb/docs/raw/{{< param "info.version" >}}/docs/examples/qdrant/reconfigure-tls/add-tls.yaml qdrantopsrequest.ops.kubedb.com/qdops-add-tls created ``` @@ -197,7 +197,7 @@ Here, - `spec.tls.rotateCertificates` specifies that we are requesting to rotate the certificates of the `qdrant-sample` database. ```bash -$ kubectl apply -f https://github.com/kubedb/docs/raw/{{< param "info.version" >}}/docs/guides/qdrant/reconfigure-tls/yamls/rotate-tls.yaml +$ kubectl apply -f https://github.com/kubedb/docs/raw/{{< param "info.version" >}}/docs/examples/qdrant/reconfigure-tls/rotate-tls.yaml qdrantopsrequest.ops.kubedb.com/qdops-rotate-tls created ``` @@ -232,7 +232,7 @@ Here, - `spec.tls.remove` specifies that we are removing the TLS configuration from `qdrant-sample` database. ```bash -$ kubectl apply -f https://github.com/kubedb/docs/raw/{{< param "info.version" >}}/docs/guides/qdrant/reconfigure-tls/yamls/remove-tls.yaml +$ kubectl apply -f https://github.com/kubedb/docs/raw/{{< param "info.version" >}}/docs/examples/qdrant/reconfigure-tls/remove-tls.yaml qdrantopsrequest.ops.kubedb.com/qdops-remove-tls created ``` diff --git a/docs/guides/qdrant/tls/configure-tls.md b/docs/guides/qdrant/tls/configure-tls.md index 09f86d5e7..a573cbc86 100644 --- a/docs/guides/qdrant/tls/configure-tls.md +++ b/docs/guides/qdrant/tls/configure-tls.md @@ -12,9 +12,9 @@ section_menu_id: guides > New to KubeDB? Please start [here](/docs/README.md). -# Configure TLS/SSL in Qdrant +# Configure TLS in Qdrant -`KubeDB` provides support for TLS/SSL encryption for `Qdrant`. This tutorial will show you how to use `KubeDB` to deploy a `Qdrant` database with TLS/SSL configuration. +`KubeDB` provides support for TLS encryption for `Qdrant`. This tutorial will show you how to use `KubeDB` to deploy a `Qdrant` database with TLS configuration. ## Before You Begin @@ -35,11 +35,22 @@ $ kubectl create ns demo namespace/demo created ``` -> **Note:** YAML files used in this tutorial are stored in [docs/guides/qdrant/tls/configure/yamls](/docs/guides/qdrant/tls/configure/yamls) directory of [kubedb/docs](https://github.com/kubedb/docs) repository. +> **Note:** YAML files used in this tutorial are stored in [docs/examples/qdrant/tls](/docs/examples/qdrant/tls) directory of [kubedb/docs](https://github.com/kubedb/docs) repository. -### Deploy Qdrant database with TLS/SSL configuration +## Overview -As a pre-requisite, we are going to create an Issuer/ClusterIssuer. This Issuer/ClusterIssuer is used to create certificates. Then we are going to deploy a Qdrant with TLS/SSL configuration. +KubeDB uses the following CRD fields to enable TLS/SSL encryption in Qdrant. + +- `spec:` + - `tls:` + - `issuerRef` + - `certificates` + - `client` — enables TLS for client-to-server communication + - `p2p` — enables TLS for peer-to-peer communication between Qdrant nodes + +- `client` (optional, default `false`): When set to `true`, the Qdrant server will accept TLS-encrypted connections from clients. This is essential for securing client access to the database. + +- `p2p` (optional, default `false`): When set to `true`, all inter-node communication within the Qdrant cluster (gossip, replication, etc.) will be encrypted using TLS. This ensures that data in transit between Qdrant nodes is secure. ### Create Issuer/ClusterIssuer @@ -74,13 +85,13 @@ spec: Let's create the `Issuer` CR we have shown above: ```bash -$ kubectl apply -f https://github.com/kubedb/docs/raw/{{< param "info.version" >}}/docs/guides/qdrant/tls/configure/yamls/issuer.yaml +$ kubectl apply -f https://github.com/kubedb/docs/raw/{{< param "info.version" >}}/docs/examples/qdrant/tls/issuer.yaml issuer.cert-manager.io/qdrant-ca-issuer created ``` -### Deploy Qdrant cluster with TLS/SSL configuration +### Deploy Qdrant cluster with TLS configuration -Here, our issuer `qdrant-ca-issuer` is ready to deploy a `Qdrant` cluster with TLS/SSL configuration. Below is the YAML for the Qdrant cluster that we are going to create: +Here, our issuer `qdrant-ca-issuer` is ready to deploy a `Qdrant` cluster with TLS configuration. Below is the YAML for the Qdrant cluster that we are going to create: ```yaml apiVersion: kubedb.com/v1alpha2 @@ -90,21 +101,15 @@ metadata: namespace: demo spec: version: "1.17.0" + mode: Distributed replicas: 3 tls: issuerRef: apiGroup: cert-manager.io name: qdrant-ca-issuer kind: Issuer - certificates: - - alias: server - subject: - organizations: - - kubedb:server - dnsNames: - - localhost - ipAddresses: - - "127.0.0.1" + client: true + p2p: true storage: storageClassName: "standard" accessModes: @@ -121,14 +126,16 @@ Here, - `apiGroup` — the API group of the issuer (e.g., `cert-manager.io`). - `kind` — the kind of issuer (`Issuer` or `ClusterIssuer`). - `name` — the name of the issuer. -- `spec.tls.certificates` provides options to configure certificate renewal and keep-alive. You can find more details from [here](/docs/guides/qdrant/concepts/qdrant.md#tls). +- `spec.tls.client` (optional, default `false`): Enables TLS for client-to-server communication. When set to `true`, clients must connect using TLS. +- `spec.tls.p2p` (optional, default `false`): Enables TLS for peer-to-peer communication between Qdrant nodes in the cluster. +- `spec.tls.certificates` provides options to configure custom certificate settings. You can find more details from [here](/docs/guides/qdrant/concepts/qdrant.md#spectls). **Deploy Qdrant Cluster:** Let's create the `Qdrant` CR we have shown above: ```bash -$ kubectl apply -f https://github.com/kubedb/docs/raw/{{< param "info.version" >}}/docs/guides/qdrant/tls/configure/yamls/tls-qdrant.yaml +$ kubectl apply -f https://github.com/kubedb/docs/raw/{{< param "info.version" >}}/docs/examples/qdrant/tls/tls-qdrant.yaml qdrant.kubedb.com/qdrant-tls created ``` @@ -152,9 +159,9 @@ qdrant-tls-1 1/1 Running 0 2m40s qdrant-tls-2 1/1 Running 0 2m20s ``` -**Verify TLS/SSL configuration:** +**Verify TLS configuration:** -Now, let's verify the TLS/SSL configuration by checking the secrets created for the Qdrant database: +Now, let's verify the TLS configuration by checking the secrets created for the Qdrant database: ```bash $ kubectl get secrets -n demo | grep qdrant-tls @@ -162,7 +169,7 @@ qdrant-tls-server-cert kubernetes.io/tls 3 3m qdrant-tls-client-cert kubernetes.io/tls 3 3m ``` -The TLS certificates have been created and the Qdrant cluster is now configured to use TLS/SSL for both client connections and peer-to-peer communication. +The TLS certificates have been created and the Qdrant cluster is now configured to use TLS for both client connections and peer-to-peer communication. ## Next Steps diff --git a/docs/guides/qdrant/tls/overview.md b/docs/guides/qdrant/tls/overview.md index 2fc173b2e..347532a46 100644 --- a/docs/guides/qdrant/tls/overview.md +++ b/docs/guides/qdrant/tls/overview.md @@ -14,36 +14,71 @@ section_menu_id: guides # Qdrant TLS Encryption -This guide will give an overview of how KubeDB supports TLS encryption for `Qdrant` databases. +**Prerequisite:** To configure TLS/SSL in `Qdrant`, `KubeDB` uses `cert-manager` to issue certificates. So first you have to make sure that the cluster has `cert-manager` installed. Install `cert-manager` in your cluster following steps [here](https://cert-manager.io/docs/installation/). -## Before You Begin +To issue a certificate, the following CRDs of `cert-manager` are used: -- You should be familiar with the following `KubeDB` concepts: - - [Qdrant](/docs/guides/qdrant/concepts/qdrant.md) - - [QdrantOpsRequest](/docs/guides/qdrant/concepts/opsrequest.md) +- `Issuer/ClusterIssuer`: Issuers and ClusterIssuers represent certificate authorities (CAs) that are able to generate signed certificates by honoring certificate signing requests. All cert-manager certificates require a referenced issuer that is in a ready condition to attempt to honor the request. You can learn more details [here](https://cert-manager.io/docs/concepts/issuer/). -## How TLS Works for Qdrant +- `Certificate`: cert-manager has the concept of Certificates that define a desired x509 certificate which will be renewed and kept up to date. You can learn more details [here](https://cert-manager.io/docs/concepts/certificate/). -KubeDB uses `cert-manager` to manage TLS certificates for Qdrant databases. The TLS configuration process consists of the following steps: +**Qdrant CRD Specification:** -1. At first, a user creates a `ClusterIssuer` or `Issuer` using `cert-manager`. +KubeDB uses the following CRD fields to enable TLS/SSL encryption in `Qdrant`. -2. The user then creates a `Qdrant` CR with the `spec.tls` field configured, pointing to the `Issuer` or `ClusterIssuer`. +- `spec:` + - `tls:` + - `issuerRef` + - `certificates` + - `client` + - `p2p` -3. `KubeDB-Provisioner` operator watches the `Qdrant` CR. +Read about the fields in detail from the [Qdrant Concepts](/docs/guides/qdrant/concepts/qdrant.md#spectls) page. -4. When the operator finds a `Qdrant` CR with `spec.tls` configured, it requests TLS certificates from `cert-manager` using the specified issuer. +`KubeDB` uses the `Issuer` or `ClusterIssuer` referenced in the `tls.issuerRef` field, and the certificate specs provided in `tls.certificates` to generate certificate secrets. These certificate secrets including `ca.crt`, `server.crt`, `tls.key`, etc. are used to configure the `Qdrant` server. -5. `cert-manager` creates the certificates and stores them in a `Secret`. +Here, -6. `KubeDB-Provisioner` operator creates the `Petset` with the TLS secrets mounted, enabling encrypted communication. +- `issuerRef` is a reference to the `Issuer` or `ClusterIssuer` CR of [cert-manager](https://cert-manager.io/docs/concepts/issuer/) that will be used by `KubeDB` to generate necessary certificates. + - `apiGroup` is the group name of the resource that is being referenced. Currently, the only supported value is `cert-manager.io`. + - `kind` is the type of resource that is being referenced. `KubeDB` supports both `Issuer` and `ClusterIssuer` as values for this field. + - `name` is the name of the resource (`Issuer` or `ClusterIssuer`) being referenced. -7. The `Qdrant` database nodes use these certificates for encrypted client-to-server and peer-to-peer communication. +- `certificates` (optional) is a list of additional certificates used to configure the Qdrant server. You can specify custom `dnsNames`, `ipAddresses`, and `subject` for server certificates. -KubeDB supports the following TLS configurations for Qdrant: +- `client` (optional, default `false`) enables TLS for client-to-server communication. When set to `true`, the Qdrant server will accept TLS-encrypted connections from clients. -- **Add TLS** — Enable TLS on an existing non-TLS Qdrant database using a `QdrantOpsRequest`. -- **Rotate TLS** — Rotate the existing TLS certificates to refresh expiring certificates. -- **Remove TLS** — Remove TLS from an existing TLS-enabled Qdrant database. +- `p2p` (optional, default `false`) enables TLS for peer-to-peer communication between Qdrant nodes. When set to `true`, inter-node communication within the Qdrant cluster will be encrypted using TLS. -In the next doc, we are going to show a step-by-step guide on configuring TLS for a Qdrant database. +## How TLS/SSL Configures in Qdrant + +The following figure shows how `KubeDB` configures TLS/SSL in Qdrant. Open the image in a new tab to see the enlarged version. + +
+Deploy Qdrant with TLS/SSL +
Fig: Deploy Qdrant with TLS/SSL
+
+ +Deploying Qdrant with TLS/SSL configuration process consists of the following steps: + +1. At first, a user creates a `Issuer/ClusterIssuer` CR. + +2. Then the user creates a `Qdrant` CR which refers to the `Issuer/ClusterIssuer` CR that the user created in the previous step. + +3. `KubeDB-Provisioner` operator watches for the `Qdrant` CR. + +4. When it finds one, it creates `Secret`, `Service`, etc. for the `Qdrant`. + +5. `KubeDB` Ops-manager operator watches for `Qdrant`(5c), `Issuer/ClusterIssuer`(5b), `Secret` and `Service`(5a). + +6. When it finds all the resources (`Qdrant`, `Issuer/ClusterIssuer`, `Secret`, `Service`), it creates `Certificates` by using `tls.issuerRef` and `tls.certificates` field specification from `Qdrant` CR. + +7. `cert-manager` watches for certificates. + +8. When it finds one, it creates certificate secrets `tls-secrets` (server, client secrets, etc.) that hold the actual certificates signed by the CA. + +9. `KubeDB-Provisioner` operator watches for the certificate secrets `tls-secrets`. + +10. When it finds all the tls-secrets, it creates the related `PetSet` so that the Qdrant database can be configured with TLS/SSL. + +In the next doc, we are going to show a step-by-step guide on how to configure a `Qdrant` database with TLS/SSL. From c269da308159f66cfcdef9274344bcc80662425a Mon Sep 17 00:00:00 2001 From: Abdullah Al Masood <98589987+al-masood@users.noreply.github.com> Date: Wed, 20 May 2026 09:57:07 +0600 Subject: [PATCH 10/10] distributed deployment completed Signed-off-by: Abdullah Al Masood <98589987+al-masood@users.noreply.github.com> --- .../backup/logical/backup-configuration.yaml | 2 +- .../logical/examples/backupconfiguration.yaml | 4 +- .../backup/logical/examples/qdrant.yaml | 2 +- .../logical/examples/restoresession.yaml | 2 +- .../volumesnapshot/backup-configuration.yaml | 2 +- .../examples/backupconfiguration.yaml | 4 +- .../volumesnapshot/examples/qdrant.yaml | 2 +- .../examples/restoresession.yaml | 2 +- .../volumesnapshot/restore-session.yaml | 2 +- .../configuration/configuration-secret.yaml | 11 + .../examples/qdrant/configuration/qdrant.yaml | 18 + .../qdrant/quickstart/distributed.yaml | 3 +- docs/guides/qdrant/backup/logical/index.md | 62 ++-- .../qdrant/backup/volumesnapshot/index.md | 48 +-- .../qdrant/configuration/using-config-file.md | 142 +++++--- .../qdrant/distributed-deployment/overview.md | 178 ++++++---- docs/guides/qdrant/monitoring/overview.md | 69 +++- .../monitoring/using-builtin-prometheus.md | 159 --------- .../monitoring/using-prometheus-operator.md | 310 ++++++++++++++---- docs/guides/qdrant/quickstart/quickstart.md | 48 +++ 20 files changed, 665 insertions(+), 405 deletions(-) create mode 100644 docs/examples/qdrant/configuration/configuration-secret.yaml create mode 100644 docs/examples/qdrant/configuration/qdrant.yaml delete mode 100644 docs/guides/qdrant/monitoring/using-builtin-prometheus.md diff --git a/docs/examples/qdrant/backup/logical/backup-configuration.yaml b/docs/examples/qdrant/backup/logical/backup-configuration.yaml index dccda2de1..bd5677c58 100644 --- a/docs/examples/qdrant/backup/logical/backup-configuration.yaml +++ b/docs/examples/qdrant/backup/logical/backup-configuration.yaml @@ -1,7 +1,7 @@ apiVersion: core.kubestash.com/v1alpha1 kind: BackupConfiguration metadata: - name: sample-qdrant-backup + name: qdrant-sample-backup namespace: default spec: target: diff --git a/docs/examples/qdrant/backup/logical/examples/backupconfiguration.yaml b/docs/examples/qdrant/backup/logical/examples/backupconfiguration.yaml index 27db6fb0c..5d5df2379 100644 --- a/docs/examples/qdrant/backup/logical/examples/backupconfiguration.yaml +++ b/docs/examples/qdrant/backup/logical/examples/backupconfiguration.yaml @@ -1,14 +1,14 @@ apiVersion: core.kubestash.com/v1alpha1 kind: BackupConfiguration metadata: - name: sample-qdrant-backup + name: qdrant-sample-backup namespace: demo spec: target: apiGroup: kubedb.com kind: Qdrant namespace: demo - name: sample-qdrant + name: qdrant-sample backends: - name: minio-backend storageRef: diff --git a/docs/examples/qdrant/backup/logical/examples/qdrant.yaml b/docs/examples/qdrant/backup/logical/examples/qdrant.yaml index 76b85efcc..040f48afa 100644 --- a/docs/examples/qdrant/backup/logical/examples/qdrant.yaml +++ b/docs/examples/qdrant/backup/logical/examples/qdrant.yaml @@ -1,7 +1,7 @@ apiVersion: kubedb.com/v1 kind: Qdrant metadata: - name: sample-qdrant + name: qdrant-sample namespace: demo spec: version: "1.17.0" diff --git a/docs/examples/qdrant/backup/logical/examples/restoresession.yaml b/docs/examples/qdrant/backup/logical/examples/restoresession.yaml index ac813c298..4460f9b9e 100644 --- a/docs/examples/qdrant/backup/logical/examples/restoresession.yaml +++ b/docs/examples/qdrant/backup/logical/examples/restoresession.yaml @@ -1,7 +1,7 @@ apiVersion: core.kubestash.com/v1alpha1 kind: RestoreSession metadata: - name: restore-sample-qdrant + name: restore-qdrant-sample namespace: demo spec: target: diff --git a/docs/examples/qdrant/backup/volumesnapshot/backup-configuration.yaml b/docs/examples/qdrant/backup/volumesnapshot/backup-configuration.yaml index 635840e6b..8d94eb39e 100644 --- a/docs/examples/qdrant/backup/volumesnapshot/backup-configuration.yaml +++ b/docs/examples/qdrant/backup/volumesnapshot/backup-configuration.yaml @@ -1,7 +1,7 @@ apiVersion: core.kubestash.com/v1alpha1 kind: BackupConfiguration metadata: - name: sample-qdrant-backup + name: qdrant-sample-backup namespace: default spec: target: diff --git a/docs/examples/qdrant/backup/volumesnapshot/examples/backupconfiguration.yaml b/docs/examples/qdrant/backup/volumesnapshot/examples/backupconfiguration.yaml index cfe5ed074..afd881a4e 100644 --- a/docs/examples/qdrant/backup/volumesnapshot/examples/backupconfiguration.yaml +++ b/docs/examples/qdrant/backup/volumesnapshot/examples/backupconfiguration.yaml @@ -1,14 +1,14 @@ apiVersion: core.kubestash.com/v1alpha1 kind: BackupConfiguration metadata: - name: sample-qdrant-backup + name: qdrant-sample-backup namespace: demo spec: target: apiGroup: kubedb.com kind: Qdrant namespace: demo - name: sample-qdrant + name: qdrant-sample backends: - name: minio-backend storageRef: diff --git a/docs/examples/qdrant/backup/volumesnapshot/examples/qdrant.yaml b/docs/examples/qdrant/backup/volumesnapshot/examples/qdrant.yaml index 76b85efcc..040f48afa 100644 --- a/docs/examples/qdrant/backup/volumesnapshot/examples/qdrant.yaml +++ b/docs/examples/qdrant/backup/volumesnapshot/examples/qdrant.yaml @@ -1,7 +1,7 @@ apiVersion: kubedb.com/v1 kind: Qdrant metadata: - name: sample-qdrant + name: qdrant-sample namespace: demo spec: version: "1.17.0" diff --git a/docs/examples/qdrant/backup/volumesnapshot/examples/restoresession.yaml b/docs/examples/qdrant/backup/volumesnapshot/examples/restoresession.yaml index af0d4cd76..d87876ff0 100644 --- a/docs/examples/qdrant/backup/volumesnapshot/examples/restoresession.yaml +++ b/docs/examples/qdrant/backup/volumesnapshot/examples/restoresession.yaml @@ -1,7 +1,7 @@ apiVersion: core.kubestash.com/v1alpha1 kind: RestoreSession metadata: - name: restore-sample-qdrant + name: restore-qdrant-sample namespace: demo spec: target: diff --git a/docs/examples/qdrant/backup/volumesnapshot/restore-session.yaml b/docs/examples/qdrant/backup/volumesnapshot/restore-session.yaml index 9352d0cee..ff13c67a5 100644 --- a/docs/examples/qdrant/backup/volumesnapshot/restore-session.yaml +++ b/docs/examples/qdrant/backup/volumesnapshot/restore-session.yaml @@ -1,7 +1,7 @@ apiVersion: core.kubestash.com/v1alpha1 kind: RestoreSession metadata: - name: restore-sample-qdrant + name: restore-qdrant-sample namespace: demo spec: target: diff --git a/docs/examples/qdrant/configuration/configuration-secret.yaml b/docs/examples/qdrant/configuration/configuration-secret.yaml new file mode 100644 index 000000000..1ee3e56ee --- /dev/null +++ b/docs/examples/qdrant/configuration/configuration-secret.yaml @@ -0,0 +1,11 @@ +apiVersion: v1 +stringData: + config.yaml: | + log_level: DEBUG + service: + max_request_size_mb: 64 +kind: Secret +metadata: + name: qdrant-configuration + namespace: demo +type: Opaque diff --git a/docs/examples/qdrant/configuration/qdrant.yaml b/docs/examples/qdrant/configuration/qdrant.yaml new file mode 100644 index 000000000..58c5ca923 --- /dev/null +++ b/docs/examples/qdrant/configuration/qdrant.yaml @@ -0,0 +1,18 @@ +apiVersion: kubedb.com/v1alpha2 +kind: Qdrant +metadata: + name: qdrant-sample + namespace: demo +spec: + version: "1.17.0" + replicas: 3 + configuration: + secretName: qdrant-configuration + storage: + storageClassName: "longhorn" + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 1Gi + deletionPolicy: WipeOut diff --git a/docs/examples/qdrant/quickstart/distributed.yaml b/docs/examples/qdrant/quickstart/distributed.yaml index 9afa2f981..9eca490b6 100644 --- a/docs/examples/qdrant/quickstart/distributed.yaml +++ b/docs/examples/qdrant/quickstart/distributed.yaml @@ -4,10 +4,11 @@ metadata: name: qdrant-sample namespace: demo spec: - version: 1.17.0 + version: "1.17.0" mode: Distributed replicas: 3 storage: + storageClassName: standard accessModes: - ReadWriteOnce resources: diff --git a/docs/guides/qdrant/backup/logical/index.md b/docs/guides/qdrant/backup/logical/index.md index ef68051d3..be96c3321 100644 --- a/docs/guides/qdrant/backup/logical/index.md +++ b/docs/guides/qdrant/backup/logical/index.md @@ -60,7 +60,7 @@ Below is the YAML of a sample `Qdrant` CRD that we are going to create for this apiVersion: kubedb.com/v1 kind: Qdrant metadata: - name: sample-qdrant + name: qdrant-sample namespace: demo spec: version: "1.17.0" @@ -80,7 +80,7 @@ Create the above `Qdrant` CR, ```bash $ kubectl apply -f https://github.com/kubedb/docs/raw/{{< param "info.version" >}}/docs/examples/qdrant/backup/logical/examples/qdrant.yaml -qdrant.kubedb.com/sample-qdrant created +qdrant.kubedb.com/qdrant-sample created ``` KubeDB will deploy a Qdrant database according to the above specification. It will also create the necessary `Secrets` and `Services` to access the database. @@ -90,20 +90,20 @@ Let's check if the database is ready to use, ```bash $ kubectl get qdrant -n demo NAME VERSION STATUS AGE -sample-qdrant 1.17.0 Ready 4m22s +qdrant-sample 1.17.0 Ready 4m22s ``` The database is `Ready`. Verify that KubeDB has created a `Secret` and a `Service` for this database using the following commands, ```bash -$ kubectl get secret -n demo -l=app.kubernetes.io/instance=sample-qdrant +$ kubectl get secret -n demo -l=app.kubernetes.io/instance=qdrant-sample NAME TYPE DATA AGE -sample-qdrant-auth Opaque 2 4m58s +qdrant-sample-auth Opaque 2 4m58s -$ kubectl get service -n demo -l=app.kubernetes.io/instance=sample-qdrant +$ kubectl get service -n demo -l=app.kubernetes.io/instance=qdrant-sample NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE -sample-qdrant ClusterIP 10.96.55.61 6333/TCP 97s -sample-qdrant-pods ClusterIP None 6333/TCP 97s +qdrant-sample ClusterIP 10.96.55.61 6333/TCP 97s +qdrant-sample-pods ClusterIP None 6333/TCP 97s ``` KubeDB creates an [AppBinding](/docs/guides/qdrant/concepts/appbinding/index.md) CR that holds the necessary information to connect with the database. @@ -115,7 +115,7 @@ Verify that the `AppBinding` has been created successfully using the following c ```bash $ kubectl get appbindings -n demo NAME AGE -sample-qdrant 9m24s +qdrant-sample 9m24s ``` **Insert Sample Data:** @@ -123,17 +123,17 @@ sample-qdrant 9m24s Now, we are going to exec into the database pod and create some sample data. At first, find out the database `Pod` using the following command, ```bash -$ kubectl get pods -n demo --selector="app.kubernetes.io/instance=sample-qdrant" +$ kubectl get pods -n demo --selector="app.kubernetes.io/instance=qdrant-sample" NAME READY STATUS RESTARTS AGE -sample-qdrant-0 1/1 Running 0 2m41s -sample-qdrant-1 1/1 Running 0 2m35s -sample-qdrant-2 1/1 Running 0 2m29s +qdrant-sample-0 1/1 Running 0 2m41s +qdrant-sample-1 1/1 Running 0 2m35s +qdrant-sample-2 1/1 Running 0 2m29s ``` Now, let's exec into the `Pod` to insert some sample data into Qdrant: ```bash -$ kubectl exec -it -n demo sample-qdrant-0 -- sh +$ kubectl exec -it -n demo qdrant-sample-0 -- sh # Upload some sample points to a collection $ wget -qO- --header 'Content-Type: application/json' \ --post-data '{ @@ -255,24 +255,24 @@ retentionpolicy.storage.kubestash.com/demo-retention created ### Backup -We have to create a `BackupConfiguration` targeting respective `sample-qdrant` Qdrant database. Then, KubeStash will create a `CronJob` for each session to take periodic backup of that database. +We have to create a `BackupConfiguration` targeting respective `qdrant-sample` Qdrant database. Then, KubeStash will create a `CronJob` for each session to take periodic backup of that database. **Create BackupConfiguration:** -Below is the YAML for `BackupConfiguration` CR to backup the `sample-qdrant` database that we have deployed earlier, +Below is the YAML for `BackupConfiguration` CR to backup the `qdrant-sample` database that we have deployed earlier, ```yaml apiVersion: core.kubestash.com/v1alpha1 kind: BackupConfiguration metadata: - name: sample-qdrant-backup + name: qdrant-sample-backup namespace: demo spec: target: apiGroup: kubedb.com kind: Qdrant namespace: demo - name: sample-qdrant + name: qdrant-sample backends: - name: minio-backend storageRef: @@ -304,14 +304,14 @@ spec: Here, - `.spec.sessions[*].schedule` specifies that we want to backup the database at `5 minutes` interval. -- `.spec.target` refers to the targeted `sample-qdrant` Qdrant database that we created earlier. +- `.spec.target` refers to the targeted `qdrant-sample` Qdrant database that we created earlier. - `.spec.sessions[*].addon.tasks[*].name` specifies that the `logical-backup` task will be executed. The `params.collections` field can be used to specify which collection(s) to backup. Let's create the `BackupConfiguration` CR that we have shown above, ```bash $ kubectl apply -f https://github.com/kubedb/docs/raw/{{< param "info.version" >}}/docs/examples/qdrant/backup/logical/examples/backupconfiguration.yaml -backupconfiguration.core.kubestash.com/sample-qdrant-backup created +backupconfiguration.core.kubestash.com/qdrant-sample-backup created ``` **Verify Backup Setup Successful:** @@ -321,7 +321,7 @@ If everything goes well, the phase of the `BackupConfiguration` should be `Ready ```bash $ kubectl get backupconfiguration -n demo NAME PHASE PAUSED AGE -sample-qdrant-backup Ready 2m50s +qdrant-sample-backup Ready 2m50s ``` Additionally, we can verify that the `Repository` specified in the `BackupConfiguration` has been created using the following command, @@ -341,7 +341,7 @@ Verify that the `CronJob` has been created using the following command, ```bash $ kubectl get cronjob -n demo NAME SCHEDULE SUSPEND ACTIVE LAST SCHEDULE AGE -trigger-sample-qdrant-backup-frequent-backup */5 * * * * 0 2m45s 3m25s +trigger-qdrant-sample-backup-frequent-backup */5 * * * * 0 2m45s 3m25s ``` **Verify BackupSession:** @@ -352,7 +352,7 @@ KubeStash triggers an instant backup as soon as the `BackupConfiguration` is rea $ kubectl get backupsession -n demo -w NAME INVOKER-TYPE INVOKER-NAME PHASE DURATION AGE -sample-qdrant-backup-frequent-backup-xyz BackupConfiguration sample-qdrant-backup Succeeded 7m22s +qdrant-sample-backup-frequent-backup-xyz BackupConfiguration qdrant-sample-backup Succeeded 7m22s ``` We can see from the above output that the backup session has succeeded. Now, we are going to verify whether the backed up data has been stored in the backend. @@ -372,7 +372,7 @@ Run the following command to check the respective `Snapshot` which represents th ```bash $ kubectl get snapshots -n demo -l=kubestash.com/repo-name=minio-qdrant-repo NAME REPOSITORY SESSION SNAPSHOT-TIME DELETION-POLICY PHASE AGE -minio-qdrant-repo-sample-qdrant-backup-frequent-backup-xyz minio-qdrant-repo frequent-backup 2024-01-23T13:10:54Z Delete Succeeded 16h +minio-qdrant-repo-qdrant-sample-backup-frequent-backup-xyz minio-qdrant-repo frequent-backup 2024-01-23T13:10:54Z Delete Succeeded 16h ``` > Note: KubeStash creates a `Snapshot` with the following labels: @@ -391,7 +391,7 @@ In this section, we are going to restore the database from the backup we have ta #### Deploy Restored Database: -Now, we have to deploy the restored database similarly as we have deployed the original `sample-qdrant` database. However, this time there will be the following differences: +Now, we have to deploy the restored database similarly as we have deployed the original `qdrant-sample` database. However, this time there will be the following differences: - We are going to specify `.spec.init.waitForInitialRestore` field that tells KubeDB to wait for first restore to complete before marking this database is ready to use. @@ -444,7 +444,7 @@ Below, is the contents of YAML file of the `RestoreSession` object that we are g apiVersion: core.kubestash.com/v1alpha1 kind: RestoreSession metadata: - name: restore-sample-qdrant + name: restore-qdrant-sample namespace: demo spec: target: @@ -468,7 +468,7 @@ Let's create the RestoreSession CRD object we have shown above, ```bash $ kubectl apply -f https://github.com/kubedb/docs/raw/{{< param "info.version" >}}/docs/examples/qdrant/backup/logical/examples/restoresession.yaml -restoresession.core.kubestash.com/restore-sample-qdrant created +restoresession.core.kubestash.com/restore-qdrant-sample created ``` Once, you have created the `RestoreSession` object, KubeStash will create restore Job. Run the following command to watch the phase of the `RestoreSession` object, @@ -478,7 +478,7 @@ $ watch kubectl get restoresession -n demo Every 2.0s: kubectl get restores... AppsCode-PC-03: Wed Aug 21 10:44:05 2024 NAME REPOSITORY FAILURE-POLICY PHASE DURATION AGE -restore-sample-qdrant minio-qdrant-repo Succeeded 3s 53s +restore-qdrant-sample minio-qdrant-repo Succeeded 3s 53s ``` The `Succeeded` phase means that the restore process has been completed successfully. @@ -520,12 +520,12 @@ So, from the above output, we can see that the `my_collection` collection we cre To cleanup the Kubernetes resources created by this tutorial, run: ```bash -kubectl delete backupconfigurations.core.kubestash.com -n demo sample-qdrant-backup -kubectl delete restoresessions.core.kubestash.com -n demo restore-sample-qdrant +kubectl delete backupconfigurations.core.kubestash.com -n demo qdrant-sample-backup +kubectl delete restoresessions.core.kubestash.com -n demo restore-qdrant-sample kubectl delete retentionpolicies.storage.kubestash.com -n demo demo-retention kubectl delete backupstorage -n demo minio-storage kubectl delete secret -n demo aws-secret kubectl delete secret -n demo encrypt-secret kubectl delete qdrant -n demo restored-qdrant -kubectl delete qdrant -n demo sample-qdrant +kubectl delete qdrant -n demo qdrant-sample ``` diff --git a/docs/guides/qdrant/backup/volumesnapshot/index.md b/docs/guides/qdrant/backup/volumesnapshot/index.md index 5e738c9bb..07dee987a 100644 --- a/docs/guides/qdrant/backup/volumesnapshot/index.md +++ b/docs/guides/qdrant/backup/volumesnapshot/index.md @@ -134,7 +134,7 @@ Below is the YAML of a sample `Qdrant` CRD that we are going to create for this apiVersion: kubedb.com/v1 kind: Qdrant metadata: - name: sample-qdrant + name: qdrant-sample namespace: demo spec: version: "1.17.0" @@ -154,7 +154,7 @@ Create the above `Qdrant` CR, ```bash $ kubectl apply -f https://github.com/kubedb/docs/raw/{{< param "info.version" >}}/docs/examples/qdrant/backup/volumesnapshot/examples/qdrant.yaml -qdrant.kubedb.com/sample-qdrant created +qdrant.kubedb.com/qdrant-sample created ``` KubeDB will deploy a Qdrant database according to the above specification. @@ -164,7 +164,7 @@ Let's check if the database is ready to use, ```bash $ kubectl get qdrant -n demo NAME VERSION STATUS AGE -sample-qdrant 1.17.0 Ready 4m22s +qdrant-sample 1.17.0 Ready 4m22s ``` **Insert Sample Data:** @@ -172,17 +172,17 @@ sample-qdrant 1.17.0 Ready 4m22s Now, we are going to exec into the database pod and create some sample data. At first, find out the database `Pod` using the following command, ```bash -$ kubectl get pods -n demo --selector="app.kubernetes.io/instance=sample-qdrant" +$ kubectl get pods -n demo --selector="app.kubernetes.io/instance=qdrant-sample" NAME READY STATUS RESTARTS AGE -sample-qdrant-0 1/1 Running 0 2m41s -sample-qdrant-1 1/1 Running 0 2m35s -sample-qdrant-2 1/1 Running 0 2m29s +qdrant-sample-0 1/1 Running 0 2m41s +qdrant-sample-1 1/1 Running 0 2m35s +qdrant-sample-2 1/1 Running 0 2m29s ``` Now, let's exec into the Pod to insert some sample data into Qdrant: ```bash -$ kubectl exec -it -n demo sample-qdrant-0 -- sh +$ kubectl exec -it -n demo qdrant-sample-0 -- sh # Upload some sample points to a collection $ wget -qO- --header 'Content-Type: application/json' \ --post-data '{ @@ -200,24 +200,24 @@ Now, we are ready to backup the database. ## Backup -We have to create a `BackupConfiguration` targeting respective `sample-qdrant` Qdrant database. Then, KubeStash will create a `CronJob` for each session to take periodic backup of that database using volume snapshots. +We have to create a `BackupConfiguration` targeting respective `qdrant-sample` Qdrant database. Then, KubeStash will create a `CronJob` for each session to take periodic backup of that database using volume snapshots. **Create BackupConfiguration:** -Below is the YAML for `BackupConfiguration` CR to backup the `sample-qdrant` database that we have deployed earlier, +Below is the YAML for `BackupConfiguration` CR to backup the `qdrant-sample` database that we have deployed earlier, ```yaml apiVersion: core.kubestash.com/v1alpha1 kind: BackupConfiguration metadata: - name: sample-qdrant-backup + name: qdrant-sample-backup namespace: demo spec: target: apiGroup: kubedb.com kind: Qdrant namespace: demo - name: sample-qdrant + name: qdrant-sample backends: - name: minio-backend storageRef: @@ -249,14 +249,14 @@ spec: Here, - `.spec.sessions[*].schedule` specifies that we want to backup the database at `5 minutes` interval. -- `.spec.target` refers to the targeted `sample-qdrant` Qdrant database that we created earlier. +- `.spec.target` refers to the targeted `qdrant-sample` Qdrant database that we created earlier. - `.spec.sessions[*].addon.tasks[*].params[*].volumeSnapshotClassName` specifies the `VolumeSnapshotClass` to use for creating volume snapshots. Let's create the `BackupConfiguration` CR that we have shown above, ```bash $ kubectl apply -f https://github.com/kubedb/docs/raw/{{< param "info.version" >}}/docs/examples/qdrant/backup/volumesnapshot/examples/backupconfiguration.yaml -backupconfiguration.core.kubestash.com/sample-qdrant-backup created +backupconfiguration.core.kubestash.com/qdrant-sample-backup created ``` **Verify Backup Setup Successful:** @@ -266,7 +266,7 @@ If everything goes well, the phase of the `BackupConfiguration` should be `Ready ```bash $ kubectl get backupconfiguration -n demo NAME PHASE PAUSED AGE -sample-qdrant-backup Ready 2m50s +qdrant-sample-backup Ready 2m50s ``` Additionally, we can verify that the `Repository` specified in the `BackupConfiguration` has been created using the following command, @@ -286,7 +286,7 @@ Verify that the `VolumeSnapshot` has been created using the following command, ```bash $ kubectl get volumesnapshot -n demo NAME READYTOUSE SOURCEPVC SOURCESNAPSHOTCONTENT RESTORESIZE SNAPSHOTCLASS SNAPSHOTCONTENT CREATIONTIME AGE -minio-qdrant-repo-xyz true data-sample-qdrant-0 1Gi longhorn-snapshot-vsc snapcontent-xyz 2m 2m +minio-qdrant-repo-xyz true data-qdrant-sample-0 1Gi longhorn-snapshot-vsc snapcontent-xyz 2m 2m ``` **Verify BackupSession:** @@ -297,7 +297,7 @@ KubeStash triggers an instant backup as soon as the `BackupConfiguration` is rea $ kubectl get backupsession -n demo -w NAME INVOKER-TYPE INVOKER-NAME PHASE DURATION AGE -sample-qdrant-backup-frequent-backup-xyz BackupConfiguration sample-qdrant-backup Succeeded 7m22s +qdrant-sample-backup-frequent-backup-xyz BackupConfiguration qdrant-sample-backup Succeeded 7m22s ``` We can see from the above output that the backup session has succeeded. @@ -308,7 +308,7 @@ In this section, we are going to restore the database from the volume snapshot b #### Deploy Restored Database: -Now, we have to deploy the restored database similarly as we have deployed the original `sample-qdrant` database. However, this time there will be the following differences: +Now, we have to deploy the restored database similarly as we have deployed the original `qdrant-sample` database. However, this time there will be the following differences: - We are going to specify `.spec.init.waitForInitialRestore` field that tells KubeDB to wait for first restore to complete before marking this database is ready to use. @@ -361,7 +361,7 @@ Below, is the contents of YAML file of the `RestoreSession` object that we are g apiVersion: core.kubestash.com/v1alpha1 kind: RestoreSession metadata: - name: restore-sample-qdrant + name: restore-qdrant-sample namespace: demo spec: target: @@ -385,7 +385,7 @@ Let's create the RestoreSession CRD object we have shown above, ```bash $ kubectl apply -f https://github.com/kubedb/docs/raw/{{< param "info.version" >}}/docs/examples/qdrant/backup/volumesnapshot/examples/restoresession.yaml -restoresession.core.kubestash.com/restore-sample-qdrant created +restoresession.core.kubestash.com/restore-qdrant-sample created ``` Once, you have created the `RestoreSession` object, KubeStash will create restore Job. Run the following command to watch the phase of the `RestoreSession` object, @@ -395,7 +395,7 @@ $ watch kubectl get restoresession -n demo Every 2.0s: kubectl get restores... AppsCode-PC-03: Wed Aug 21 10:44:05 2024 NAME REPOSITORY FAILURE-POLICY PHASE DURATION AGE -restore-sample-qdrant minio-qdrant-repo Succeeded 3s 53s +restore-qdrant-sample minio-qdrant-repo Succeeded 3s 53s ``` The `Succeeded` phase means that the restore process has been completed successfully. @@ -437,12 +437,12 @@ So, from the above output, we can see that the `my_collection` collection we cre To cleanup the Kubernetes resources created by this tutorial, run: ```bash -kubectl delete backupconfigurations.core.kubestash.com -n demo sample-qdrant-backup -kubectl delete restoresessions.core.kubestash.com -n demo restore-sample-qdrant +kubectl delete backupconfigurations.core.kubestash.com -n demo qdrant-sample-backup +kubectl delete restoresessions.core.kubestash.com -n demo restore-qdrant-sample kubectl delete retentionpolicies.storage.kubestash.com -n demo demo-retention kubectl delete backupstorage -n demo minio-storage kubectl delete secret -n demo aws-secret kubectl delete secret -n demo encrypt-secret kubectl delete qdrant -n demo restored-qdrant -kubectl delete qdrant -n demo sample-qdrant +kubectl delete qdrant -n demo qdrant-sample ``` diff --git a/docs/guides/qdrant/configuration/using-config-file.md b/docs/guides/qdrant/configuration/using-config-file.md index cf1c48641..4be95488a 100644 --- a/docs/guides/qdrant/configuration/using-config-file.md +++ b/docs/guides/qdrant/configuration/using-config-file.md @@ -29,55 +29,71 @@ $ kubectl create ns demo namespace/demo created ``` -> **Note:** YAML files used in this tutorial are stored in [docs/examples/qdrant/configuration](https://github.com/kubedb/docs/tree/{{< param "info.version" >}}/docs/examples/qdrant/configuration) folder in GitHub repository [kubedb/docs](https://github.com/kubedb/docs). +> **Note:** YAML files used in this tutorial are stored in [docs/examples/qdrant/configuration](/docs/examples/qdrant/configuration) directory of [kubedb/docs](https://github.com/kubedb/docs) repository. ## Overview -Qdrant allows configuring the database via a YAML configuration file named `production.yaml`. When the Qdrant Docker image starts, it merges configuration from the default `config.yaml` with any `production.yaml` file present. KubeDB takes advantage of this feature to allow users to provide their custom configuration. To know more about configuring Qdrant, see [here](https://qdrant.tech/documentation/guides/configuration/). +KubeDB supports three ways to provide custom configuration for Qdrant: -At first, you have to create a config file named `production.yaml` with your desired configuration. Then create a Secret with this configuration file and provide its name in `spec.configSecret.name`. The operator reads this Secret and mounts it into the Qdrant pods automatically. +| Method | Field | Priority | +|--------|-------|----------| +| **Config Secret** | `spec.configuration.secretName` | Medium | +| **Inline Config** | `spec.configuration.inline` | Highest | +| **Default Config** | (built into the Docker image) | Lowest | -In this tutorial, we will configure `log_level` and `service.max_request_size_mb` via a custom config file. +The priority order is: **Inline Config > Config Secret > Default Config**. When multiple configuration sources specify the same key, the value from the higher-priority source takes precedence. Inline config values are applied last, overriding any values from the config Secret or defaults. -## Custom Configuration +To know more about configuring Qdrant, see [here](https://qdrant.tech/documentation/guides/configuration/). -At first, let's create a `production.yaml` file with custom settings: +In this tutorial, we will configure `log_level` and `service.max_request_size_mb` using both a config Secret and inline configuration. + +## Custom Configuration via Config Secret + +At first, create a Secret with your custom configuration. Below is the YAML of the `Secret` that we are going to create: ```yaml -log_level: INFO -service: - max_request_size_mb: 64 -storage: - performance: - max_search_threads: 4 +apiVersion: v1 +stringData: + config.yaml: | + log_level: DEBUG + service: + max_request_size_mb: 64 +kind: Secret +metadata: + name: qdrant-configuration + namespace: demo +type: Opaque ``` -Now, create a Secret with this configuration file: +Let's create the `Secret` we have shown above: ```bash -$ kubectl create secret generic -n demo qdrant-config \ - --from-file=production.yaml=./production.yaml -secret/qdrant-config created +$ kubectl apply -f https://github.com/kubedb/docs/raw/{{< param "info.version" >}}/docs/examples/qdrant/configuration/configuration-secret.yaml +secret/qdrant-configuration created ``` Verify the Secret has the configuration file: ```yaml -$ kubectl get secret -n demo qdrant-config -o yaml +$ kubectl get secret -n demo qdrant-configuration -o yaml apiVersion: v1 data: - production.yaml: bG9nX2xldmVsOiBJTkZPCnNlcnZpY2U6CiAgbWF4X3JlcXVlc3Rfc2l6ZV9tYjogNjQK... + config.yaml: bG9nX2xldmVsOiBERUJVRwpzZXJ2aWNlOgogIG1heF9yZXF1ZXN0X3NpemVfbWI6IDY0Cg== kind: Secret metadata: - name: qdrant-config + creationTimestamp: "2026-05-19T06:39:07Z" + name: qdrant-configuration namespace: demo + resourceVersion: "3834858" + uid: 9cad78b6-e0e3-4e3e-b999-943c01bfb09c +type: Opaque ``` -Now, create the `Qdrant` CR specifying `spec.configSecret.name` field: +Now, create the `Qdrant` CR specifying `spec.configuration.secretName` field: ```bash -$ kubectl apply -f https://github.com/kubedb/docs/raw/{{< param "info.version" >}}/docs/examples/qdrant/configuration/qdrant-configuration.yaml -qdrant.kubedb.com/custom-qdrant created +$ kubectl apply -f https://github.com/kubedb/docs/raw/{{< param "info.version" >}}/docs/examples/qdrant/configuration/qdrant.yaml +qdrant.kubedb.com/qdrant-sample created ``` Below is the YAML for the `Qdrant` CR we just created: @@ -86,57 +102,93 @@ Below is the YAML for the `Qdrant` CR we just created: apiVersion: kubedb.com/v1alpha2 kind: Qdrant metadata: - name: custom-qdrant + name: qdrant-sample namespace: demo spec: version: "1.17.0" replicas: 3 - configSecret: - name: qdrant-config + configuration: + secretName: qdrant-configuration storage: - storageClassName: "standard" + storageClassName: "longhorn" accessModes: - - ReadWriteOnce + - ReadWriteOnce resources: requests: storage: 1Gi deletionPolicy: WipeOut ``` -Now, wait a few minutes. KubeDB operator will create the necessary PVC, Petset, services, and secrets. If everything goes well, we will see that a pod with the name `custom-qdrant-0` has been created. - -Check that the Petset's pod is running: +Now, wait a few minutes. KubeDB operator will create the necessary PVC, PetSet, services, and secrets. Let's check the status: ```bash -$ kubectl get pod -n demo custom-qdrant-0 -NAME READY STATUS RESTARTS AGE -custom-qdrant-0 1/1 Running 0 2m +$ kubectl get qdrant -n demo +NAME VERSION STATUS AGE +qdrant-sample 1.17.0 Ready 68s ``` -Now, wait for the `Qdrant` CR to go into `Ready` state: +Check that all pods are running: ```bash -$ kubectl get qdrant -n demo custom-qdrant -NAME VERSION STATUS AGE -custom-qdrant 1.17.0 Ready 3m +$ kubectl get pod -n demo +NAME READY STATUS RESTARTS AGE +qdrant-sample-0 1/1 Running 0 61s +qdrant-sample-1 1/1 Running 0 57s +qdrant-sample-2 1/1 Running 0 42s ``` -We can check that the Qdrant database is running with our custom configuration by accessing the telemetry endpoint: +Now, let's verify that the custom configuration has been applied by checking the config file inside the pod: ```bash -$ kubectl port-forward -n demo pod/custom-qdrant-0 6333:6333 & -$ curl http://localhost:6333/telemetry | jq '.result.app.max_request_size_mb' -64 +$ kubectl exec -n demo qdrant-sample-0 -- cat /qdrant/config/config.yaml +log_level: DEBUG +service: + max_request_size_mb: 64 ``` -The output confirms the database is using our custom `max_request_size_mb` value of `64`. +The output confirms the database is running with our custom `log_level` and `max_request_size_mb` values. + +As noted in the [Overview](#overview), inline configuration has the highest priority. If both a config Secret and inline config specify the same key, the inline value takes precedence. + +## Inline Configuration + +You can also provide custom configuration inline within the `Qdrant` CR using `spec.configuration.inline`. This is useful for simple config changes without creating a separate Secret. + +Below is an example YAML of a `Qdrant` CR with inline configuration: + +```yaml +apiVersion: kubedb.com/v1alpha2 +kind: Qdrant +metadata: + name: qdrant-sample + namespace: demo +spec: + version: "1.17.0" + replicas: 3 + configuration: + inline: + log_level: DEBUG + max_request_size_mb: "64" + storage: + storageClassName: "longhorn" + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 1Gi + deletionPolicy: WipeOut +``` + +> **Note:** The `inline` field is a `map[string]string`, so values must be strings. To set the config key `max_request_size_mb` to `64`, write `max_request_size_mb: "64"`. The config Secret method (shown above) supports full nested YAML structure. + +When both `spec.configuration.secretName` and `spec.configuration.inline` are set, the inline values override the corresponding keys from the config Secret. Keys not specified in inline retain their values from the config Secret. ## Cleaning up -To cleanup the Kubernetes resources created by this tutorial, run: +To clean up the Kubernetes resources created by this tutorial, run: ```bash -kubectl delete qdrant -n demo custom-qdrant -kubectl delete secret -n demo qdrant-config +kubectl delete qdrant -n demo qdrant-sample +kubectl delete secret -n demo qdrant-configuration kubectl delete ns demo ``` \ No newline at end of file diff --git a/docs/guides/qdrant/distributed-deployment/overview.md b/docs/guides/qdrant/distributed-deployment/overview.md index b12ddb5d8..0fc3e53ea 100644 --- a/docs/guides/qdrant/distributed-deployment/overview.md +++ b/docs/guides/qdrant/distributed-deployment/overview.md @@ -39,11 +39,13 @@ We will need to provide `StorageClass` in the Qdrant CR specification. Check ava ```bash $ kubectl get storageclass -NAME PROVISIONER RECLAIMPOLICY VOLUMEBINDINGMODE ALLOWVOLUMEEXPANSION AGE -standard (default) rancher.io/local-path Delete WaitForFirstConsumer false 10d +NAME PROVISIONER RECLAIMPOLICY VOLUMEBINDINGMODE ALLOWVOLUMEEXPANSION AGE +local-path (default) rancher.io/local-path Delete WaitForFirstConsumer false 29d +longhorn (default) driver.longhorn.io Delete Immediate true 26d +standard rancher.io/local-path Delete WaitForFirstConsumer false 21h ``` -Here, we have `standard` StorageClass in our cluster. +We will use `standard` StorageClass in this tutorial. ## Find Available QdrantVersion @@ -51,8 +53,10 @@ When you install KubeDB, it creates `QdrantVersion` CRDs for all supported Qdran ```bash $ kubectl get qdrantversions -NAME VERSION DB_IMAGE DEPRECATED AGE -1.17.0 1.17.0 docker.io/qdrant/qdrant:v1.17.0-unprivileged 13d +NAME VERSION DB_IMAGE DEPRECATED AGE +1.15.4 1.15.4 docker.io/qdrant/qdrant:v1.15.4-unprivileged 29d +1.16.2 1.16.2 docker.io/qdrant/qdrant:v1.16.2-unprivileged 29d +1.17.0 1.17.0 docker.io/qdrant/qdrant:v1.17.0-unprivileged 29d ``` In this tutorial, we will use `1.17.0` QdrantVersion CR to create a distributed Qdrant cluster. @@ -70,27 +74,16 @@ metadata: name: qdrant-sample namespace: demo spec: - version: 1.17.0 + version: "1.17.0" mode: Distributed replicas: 3 storage: - storageClassName: longhorn + storageClassName: standard accessModes: - ReadWriteOnce resources: requests: - storage: 200Mi - podTemplate: - spec: - containers: - - name: qdrant - resources: - requests: - cpu: 100m - memory: 100Mi - limits: - cpu: 100m - memory: 100Mi + storage: 2Gi deletionPolicy: WipeOut ``` @@ -99,12 +92,11 @@ Here, - `spec.mode` set to `Distributed` enables distributed mode - `spec.replicas` specifies the number of Qdrant nodes (default is 1) - `spec.storage` specifies the storage configuration for each node -- `spec.podTemplate` allows setting resource limits and requests Let's create the Qdrant object: ```bash -$ kubectl create -f https://github.com/kubedb/docs/raw/{{< param "info.version" >}}/docs/guides/qdrant/distributed-deployment/yamls/qdrant-distributed.yaml +$ kubectl apply -f https://github.com/kubedb/docs/raw/{{< param "info.version" >}}/docs/examples/qdrant/quickstart/distributed.yaml qdrant.kubedb.com/qdrant-sample created ``` @@ -114,69 +106,139 @@ Let's check the status of the Qdrant object: ```bash $ kubectl get qdrant -n demo -NAME VERSION STATUS AGE -qdrant-sample 1.17.0 Ready 2m +NAME VERSION STATUS AGE +qdrant-sample 1.17.0 Ready 50s ``` To see the distributed nodes, check the pods: ```bash -$ kubectl get pods -n demo -l app.kubernetes.io/instance=qdrant-sample -NAME READY STATUS RESTARTS AGE -qdrant-sample-0 1/1 Running 0 2m -qdrant-sample-1 1/1 Running 0 2m -qdrant-sample-2 1/1 Running 0 2m +$ kubectl get pod -n demo -l app.kubernetes.io/instance=qdrant-sample +NAME READY STATUS RESTARTS AGE +qdrant-sample-0 1/1 Running 0 48s +qdrant-sample-1 1/1 Running 0 43s +qdrant-sample-2 1/1 Running 0 39s ``` In distributed mode, Qdrant creates a Petset with the specified number of replicas. -## Horizontal Scaling +## Interact with the Distributed Cluster -You can scale the Qdrant cluster horizontally by increasing or decreasing the number of replicas using `QdrantOpsRequest`. +Now let's interact with the distributed Qdrant cluster. First, get the API key and forward a port: -To scale up to 5 nodes: +```bash +$ kubectl get secret -n demo qdrant-sample-auth -o jsonpath='{.data.api-key}' | base64 -d +F1UxwGOleYzmofu3 -```yaml -apiVersion: ops.kubedb.com/v1alpha1 -kind: QdrantOpsRequest -metadata: - name: qdrant-hor-scaling - namespace: demo -spec: - type: HorizontalScaling - databaseRef: - name: qdrant-sample - horizontalScaling: - node: 5 +$ kubectl port-forward -n demo svc/qdrant-sample 6333:6333 & ``` -Apply the scaling request: +Create a collection with sharding and replication: ```bash -$ kubectl create -f https://github.com/kubedb/docs/raw/{{< param "info.version" >}}/docs/guides/qdrant/distributed-deployment/yamls/qdrant-hor-scaling.yaml -qdrantopsrequest.ops.kubedb.com/qdrant-hor-scaling created +$ curl -X PUT http://localhost:6333/collections/demo_vectors \ + -H "Content-Type: application/json" \ + -H "api-key: F1UxwGOleYzmofu3" \ + -d '{ + "shard_number": 6, + "replication_factor": 2, + "vectors": { + "size": 8, + "distance": "Cosine" + } + }' +{"result":true,"status":"ok","time":0.912871278} ``` -Monitor the scaling operation: +Add some points to the collection: ```bash -$ kubectl get qdrantopsrequest -n demo -NAME TYPE PHASE AGE -qdrant-hor-scaling HorizontalScaling Done 2m +$ curl -X PUT "http://localhost:6333/collections/demo_vectors/points?wait=true" \ + -H "Content-Type: application/json" \ + -H "api-key: F1UxwGOleYzmofu3" \ + -d '{ + "points": [ + {"id": 1, "vector": [0.15, 0.22, 0.31, 0.44, 0.51, 0.68, 0.73, 0.89], "payload": {"label": "apple"}}, + {"id": 2, "vector": [0.12, 0.28, 0.35, 0.42, 0.53, 0.64, 0.71, 0.85], "payload": {"label": "banana"}}, + {"id": 3, "vector": [0.18, 0.21, 0.33, 0.46, 0.50, 0.66, 0.77, 0.82], "payload": {"label": "cherry"}}, + {"id": 4, "vector": [0.14, 0.25, 0.32, 0.41, 0.54, 0.63, 0.75, 0.88], "payload": {"label": "date"}}, + {"id": 5, "vector": [0.16, 0.23, 0.38, 0.43, 0.55, 0.61, 0.72, 0.86], "payload": {"label": "elderberry"}} + ] + }' +{"result":{"operation_id":1,"status":"completed"},"status":"ok","time":0.002696282} ``` -Verify the new replica count: +Verify that clustering is enabled by checking the root cluster endpoint: ```bash -$ kubectl get pods -n demo -l app.kubernetes.io/instance=qdrant-sample -NAME READY STATUS RESTARTS AGE -qdrant-sample-0 1/1 Running 0 5m -qdrant-sample-1 1/1 Running 0 5m -qdrant-sample-2 1/1 Running 0 5m -qdrant-sample-3 1/1 Running 0 2m -qdrant-sample-4 1/1 Running 0 2m +$ curl http://localhost:6333/cluster -H "api-key: F1UxwGOleYzmofu3" | jq +{ + "result": { + "status": "enabled", + "peer_id": 5887768058245046, + "peers": { + "6780901721144166": { + "uri": "http://qdrant-sample-2.qdrant-sample-pods.demo.svc.cluster.local:6335/" + }, + "5887768058245046": { + "uri": "http://qdrant-sample-0.qdrant-sample-pods.demo.svc.cluster.local:6335/" + }, + "5462954126296684": { + "uri": "http://qdrant-sample-1.qdrant-sample-pods.demo.svc.cluster.local:6335/" + } + }, + "raft_info": { + "term": 1, + "commit": 27, + "pending_operations": 0, + "leader": 5887768058245046, + "role": "Leader", + "is_voter": true + }, + "consensus_thread_status": { + "consensus_thread_status": "working" + }, + "message_send_failures": {} + }, + "status": "ok" +} ``` +The output confirms distributed mode is **enabled** with 3 peers and Raft consensus active. + +Now check the collection-level shard distribution: + +```bash +$ curl http://localhost:6333/collections/demo_vectors/cluster \ + -H "api-key: F1UxwGOleYzmofu3" | jq +{ + "result": { + "peer_id": 5887768058245046, + "shard_count": 6, + "local_shards": [ + {"shard_id": 0, "points_count": 1, "state": "Active"}, + {"shard_id": 2, "points_count": 1, "state": "Active"}, + {"shard_id": 3, "points_count": 2, "state": "Active"}, + {"shard_id": 5, "points_count": 0, "state": "Active"} + ], + "remote_shards": [ + {"shard_id": 0, "peer_id": 6780901721144166, "state": "Active"}, + {"shard_id": 1, "peer_id": 6780901721144166, "state": "Active"}, + {"shard_id": 1, "peer_id": 5462954126296684, "state": "Active"}, + {"shard_id": 2, "peer_id": 5462954126296684, "state": "Active"}, + {"shard_id": 3, "peer_id": 6780901721144166, "state": "Active"}, + {"shard_id": 4, "peer_id": 5462954126296684, "state": "Active"}, + {"shard_id": 4, "peer_id": 6780901721144166, "state": "Active"}, + {"shard_id": 5, "peer_id": 5462954126296684, "state": "Active"} + ], + "shard_transfers": [] + }, + "status": "ok" +} +``` + +The output shows that the collection `demo_vectors` is distributed across 3 peers with 6 shards and a replication factor of 2. Each shard is in `Active` state, and local/remote shards are balanced across the cluster nodes. + ## Cleaning Up To delete the Qdrant database and all associated resources: diff --git a/docs/guides/qdrant/monitoring/overview.md b/docs/guides/qdrant/monitoring/overview.md index 21007e9f1..cbb0ac6d8 100644 --- a/docs/guides/qdrant/monitoring/overview.md +++ b/docs/guides/qdrant/monitoring/overview.md @@ -12,27 +12,68 @@ section_menu_id: guides > New to KubeDB? Please start [here](/docs/README.md). -# Qdrant Monitoring Overview +# Monitoring Qdrant with KubeDB -This guide will give an overview of how KubeDB supports monitoring for `Qdrant` databases. +KubeDB has native support for monitoring via [Prometheus](https://prometheus.io/). You can use builtin [Prometheus](https://github.com/prometheus/prometheus) scraper or [Prometheus operator](https://github.com/prometheus-operator/prometheus-operator) to monitor KubeDB managed databases. This tutorial will show you how database monitoring works with KubeDB and how to configure Database CR to enable monitoring. -## Before You Begin +## Overview -- You should be familiar with the following `KubeDB` concepts: - - [Qdrant](/docs/guides/qdrant/concepts/qdrant.md) +KubeDB uses Prometheus [exporter](https://prometheus.io/docs/instrumenting/exporters/#databases) images to export Prometheus metrics for respective databases. Following diagram shows the logical flow of database monitoring with KubeDB. -## How KubeDB Monitoring Works +

+  Database Monitoring Flow +

-KubeDB uses Prometheus to monitor `Qdrant` databases. KubeDB operator watches the `Qdrant` CR and sets up monitoring as follows: +When a user creates a database CR with `spec.monitor` section configured, KubeDB operator provisions the respective database and injects an exporter image as sidecar to the database pod. It also creates a dedicated stats service with name `{database-crd-name}-stats` for monitoring. Prometheus server can scrape metrics using this stats service. -1. When a `Qdrant` database is deployed with `spec.monitor` configured, KubeDB creates a dedicated `stats` service (or uses the existing service) with the appropriate annotations for Prometheus scraping. +## Configure Monitoring -2. KubeDB supports two monitoring approaches: - - **Builtin Prometheus** — uses Prometheus' built-in auto-discovery mechanism (`prometheus.io/scrape` annotations on the stats service). - - **Prometheus Operator** — creates a `ServiceMonitor` CR that is picked up by the Prometheus Operator. +In order to enable monitoring for a database, you have to configure `spec.monitor` section. KubeDB provides following options to configure `spec.monitor` section: -3. The Qdrant stats service exposes Prometheus-compatible metrics at the `/metrics` endpoint, including metrics about collections, vectors, memory usage, and gRPC/REST request performance. +| Field | Type | Uses | +| -------------------------------------------------- | ---------- | --------------------------------------------------------------------------------------------------------------------------------------------- | +| `spec.monitor.agent` | `Required` | Type of the monitoring agent that will be used to monitor this database. It can be `prometheus.io/builtin` or `prometheus.io/operator`. | +| `spec.monitor.prometheus.exporter.port` | `Optional` | Port number where the exporter side car will serve metrics. | +| `spec.monitor.prometheus.exporter.args` | `Optional` | Arguments to pass to the exporter sidecar. | +| `spec.monitor.prometheus.exporter.env` | `Optional` | List of environment variables to set in the exporter sidecar container. | +| `spec.monitor.prometheus.exporter.resources` | `Optional` | Resources required by exporter sidecar container. | +| `spec.monitor.prometheus.exporter.securityContext` | `Optional` | Security options the exporter should run with. | +| `spec.monitor.prometheus.serviceMonitor.labels` | `Optional` | Labels for `ServiceMonitor` CR. | +| `spec.monitor.prometheus.serviceMonitor.interval` | `Optional` | Interval at which metrics should be scraped. | -4. Prometheus scrapes the metrics from the stats service and makes them available for alerting and dashboards. +## Sample Configuration -In the next docs, we are going to show step-by-step guides on monitoring a Qdrant database using Builtin Prometheus and Prometheus Operator. +A sample YAML for Qdrant CR with `spec.monitor` section configured to enable monitoring with [Prometheus operator](https://github.com/prometheus-operator/prometheus-operator) is shown below. + +```yaml +apiVersion: kubedb.com/v1alpha2 +kind: Qdrant +metadata: + name: qdrant-monitoring + namespace: demo +spec: + version: "1.17.0" + replicas: 3 + storage: + storageClassName: standard + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 1Gi + monitor: + agent: prometheus.io/operator + prometheus: + serviceMonitor: + labels: + release: prometheus + interval: 10s + deletionPolicy: WipeOut +``` + +Here, we have specified that we are going to monitor this Qdrant cluster using Prometheus operator through `spec.monitor.agent: prometheus.io/operator`. KubeDB will create a `ServiceMonitor` CR in the same namespace and this `ServiceMonitor` will have `release: prometheus` label. + +## Next Steps + +- Learn how to monitor Qdrant with KubeDB using [Builtin Prometheus](/docs/guides/qdrant/monitoring/using-builtin-prometheus.md). +- Learn how to monitor Qdrant with KubeDB using [Prometheus operator](/docs/guides/qdrant/monitoring/using-prometheus-operator.md). diff --git a/docs/guides/qdrant/monitoring/using-builtin-prometheus.md b/docs/guides/qdrant/monitoring/using-builtin-prometheus.md deleted file mode 100644 index 9ddfe45ef..000000000 --- a/docs/guides/qdrant/monitoring/using-builtin-prometheus.md +++ /dev/null @@ -1,159 +0,0 @@ ---- -title: Monitor Qdrant using Builtin Prometheus Discovery -menu: - docs_{{ .version }}: - identifier: qdrant-using-builtin-prometheus-monitoring - name: Builtin Prometheus - parent: qdrant-monitoring - weight: 20 -menu_name: docs_{{ .version }} -section_menu_id: guides ---- - -> New to KubeDB? Please start [here](/docs/README.md). - -# Monitoring Qdrant with Builtin Prometheus - -This tutorial will show you how to monitor `Qdrant` database using builtin [Prometheus](https://github.com/prometheus/prometheus) scraper. - -## Before You Begin - -- At first, you need to have a Kubernetes cluster, and the `kubectl` command-line tool must be configured to communicate with your cluster. If you do not already have a cluster, you can create one by using [kind](https://kind.sigs.k8s.io/docs/user/quick-start/). - -- Install `KubeDB` operator in your cluster following the steps [here](/docs/setup/README.md). - -- If you are not familiar with how to configure Prometheus to scrape metrics from various Kubernetes resources, please read the tutorial from [here](https://github.com/appscode/third-party-tools/tree/master/monitoring/prometheus/builtin). - -- To learn how Prometheus monitoring works with KubeDB in general, please visit [here](/docs/guides/qdrant/monitoring/overview.md). - -- To keep Prometheus resources isolated, we are going to use a separate namespace called `monitoring` to deploy respective monitoring resources. We are going to deploy the database in `demo` namespace. - -```bash -$ kubectl create ns monitoring -namespace/monitoring created - -$ kubectl create ns demo -namespace/demo created -``` - -> **Note:** YAML files used in this tutorial are stored in [docs/examples/qdrant/monitoring](https://github.com/kubedb/docs/tree/{{< param "info.version" >}}/docs/examples/qdrant/monitoring) folder in GitHub repository [kubedb/docs](https://github.com/kubedb/docs). - -## Deploy Qdrant with Monitoring Enabled - -At first, let's deploy a `Qdrant` database with monitoring enabled. Below is the `Qdrant` object that we are going to create: - -```yaml -apiVersion: kubedb.com/v1alpha2 -kind: Qdrant -metadata: - name: builtin-prom-qdrant - namespace: demo -spec: - version: "1.17.0" - replicas: 3 - storage: - storageClassName: "standard" - accessModes: - - ReadWriteOnce - resources: - requests: - storage: 1Gi - monitor: - agent: prometheus.io/builtin - deletionPolicy: WipeOut -``` - -Here, - -- `spec.monitor.agent: prometheus.io/builtin` specifies that we are going to monitor this server using builtin Prometheus scraper. - -Let's create the `Qdrant` CR we have shown above: - -```bash -$ kubectl apply -f https://github.com/kubedb/docs/raw/{{< param "info.version" >}}/docs/examples/qdrant/monitoring/builtin-prom-qdrant.yaml -qdrant.kubedb.com/builtin-prom-qdrant created -``` - -Now, wait for the database to go into `Ready` state: - -```bash -$ kubectl get qdrant -n demo builtin-prom-qdrant -NAME VERSION STATUS AGE -builtin-prom-qdrant 1.17.0 Ready 1m -``` - -KubeDB will create a separate stats service with name `{Qdrant cr name}-stats` for monitoring purpose. - -```bash -$ kubectl get svc -n demo --selector="app.kubernetes.io/instance=builtin-prom-qdrant" -NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE -builtin-prom-qdrant ClusterIP 10.102.7.190 6333/TCP 87s -builtin-prom-qdrant-stats ClusterIP 10.102.128.153 6333/TCP 56s -``` - -Here, `builtin-prom-qdrant-stats` service has been created for monitoring purpose. Let's describe the service: - -```bash -$ kubectl describe svc -n demo builtin-prom-qdrant-stats -Name: builtin-prom-qdrant-stats -Namespace: demo -Labels: app.kubernetes.io/component=database - app.kubernetes.io/instance=builtin-prom-qdrant - app.kubernetes.io/managed-by=kubedb.com - app.kubernetes.io/name=qdrants.kubedb.com -Annotations: monitoring.appscode.com/agent: prometheus.io/builtin - prometheus.io/path: /metrics - prometheus.io/port: 6333 - prometheus.io/scrape: true -Selector: app.kubernetes.io/instance=builtin-prom-qdrant,app.kubernetes.io/name=qdrants.kubedb.com -Type: ClusterIP -Port: metrics 6333/TCP -TargetPort: metrics/TCP -Endpoints: 10.244.1.5:6333,10.244.1.6:6333,10.244.1.7:6333 -``` - -You can see that the service contains the following annotations: - -``` -prometheus.io/scrape: true -prometheus.io/path: /metrics -prometheus.io/port: 6333 -``` - -The Prometheus server will discover this service endpoint using these annotations and will scrape metrics from all endpoints. - -## Configure Prometheus to Scrape - -To get the monitoring of this `Qdrant` database, you need to configure a Prometheus server. Below is the necessary configuration: - -```yaml -global: - scrape_interval: 15s - -scrape_configs: -- job_name: 'kubedb-databases' - kubernetes_sd_configs: - - role: endpoints - relabel_configs: - - source_labels: [__meta_kubernetes_service_annotation_prometheus_io_scrape] - regex: true - action: keep - - source_labels: [__meta_kubernetes_service_annotation_prometheus_io_path] - regex: (.+) - target_label: __metrics_path__ - action: replace -``` - -## Verify Monitoring - -Once Prometheus is configured and running, you can check the monitoring is working by navigating to the Prometheus dashboard (by default at `localhost:9090`). You should see the `builtin-prom-qdrant-stats` endpoint in the list of scrape targets. - -## Cleaning up - -To clean up the Kubernetes resources created by this tutorial, run: - -```bash -kubectl delete qdrant -n demo builtin-prom-qdrant -kubectl delete ns demo -kubectl delete ns monitoring -``` \ No newline at end of file diff --git a/docs/guides/qdrant/monitoring/using-prometheus-operator.md b/docs/guides/qdrant/monitoring/using-prometheus-operator.md index d4e50183b..7e04ad1e1 100644 --- a/docs/guides/qdrant/monitoring/using-prometheus-operator.md +++ b/docs/guides/qdrant/monitoring/using-prometheus-operator.md @@ -5,92 +5,184 @@ menu: identifier: qdrant-using-prometheus-operator-monitoring name: Prometheus Operator parent: qdrant-monitoring - weight: 30 + weight: 15 menu_name: docs_{{ .version }} section_menu_id: guides --- > New to KubeDB? Please start [here](/docs/README.md). -# Monitoring Qdrant Using Prometheus Operator +# Monitoring Qdrant Using Prometheus operator -[Prometheus operator](https://github.com/prometheus-operator/prometheus-operator) provides a simple and Kubernetes-native way to deploy and configure Prometheus server. This tutorial will show you how to use Prometheus operator to monitor `Qdrant` database deployed with KubeDB. +[Prometheus operator](https://github.com/prometheus-operator/prometheus-operator) provides simple and Kubernetes native way to deploy and configure Prometheus server. This tutorial will show you how to use Prometheus operator to monitor Qdrant deployed with KubeDB. ## Before You Begin -- At first, you need to have a Kubernetes cluster, and the `kubectl` command-line tool must be configured to communicate with your cluster. If you do not already have a cluster, you can create one by using [kind](https://kind.sigs.k8s.io/docs/user/quick-start/). +- You need to have a Kubernetes cluster, and the kubectl command-line tool must be configured to communicate with your cluster. If you do not already have a cluster, you can create one by using [kind](https://kind.sigs.k8s.io/docs/user/quick-start/). -- To learn how Prometheus monitoring works with KubeDB in general, please visit [here](/docs/guides/qdrant/monitoring/overview.md). +- Now, install KubeDB cli on your workstation and KubeDB operator in your cluster following the steps [here](/docs/setup/README.md). -- To keep Prometheus resources isolated, we are going to use a separate namespace called `monitoring` to deploy respective monitoring resources. We are going to deploy the database in `demo` namespace. +- To learn how Prometheus monitoring works with KubeDB in general, please visit [here](/docs/guides/qdrant/monitoring/overview.md). -```bash -$ kubectl create ns monitoring -namespace/monitoring created +- To keep Prometheus resources isolated, we are going to use a separate namespace called `monitoring` to deploy respective monitoring resources. We are going to deploy database in `demo` namespace. -$ kubectl create ns demo -namespace/demo created -``` + ```bash + $ kubectl create ns monitoring + namespace/monitoring created -- We need a [Prometheus operator](https://github.com/prometheus-operator/prometheus-operator) instance running. If you don't already have a running instance, deploy one following the docs from [here](https://github.com/appscode/third-party-tools/blob/master/monitoring/prometheus/operator/README.md). + $ kubectl create ns demo + namespace/demo created + ``` -- If you don't already have a Prometheus server running, deploy one following the tutorial from [here](https://github.com/appscode/third-party-tools/blob/master/monitoring/prometheus/operator/README.md#deploy-prometheus-server). +- We need a [Prometheus operator](https://github.com/prometheus-operator/prometheus-operator) instance running. If you don't already have a running instance, you can deploy one using this helm chart [here](https://github.com/prometheus-community/helm-charts/tree/main/charts/kube-prometheus-stack). -> **Note:** YAML files used in this tutorial are stored in [docs/examples/qdrant/monitoring](https://github.com/kubedb/docs/tree/{{< param "info.version" >}}/docs/examples/qdrant/monitoring) folder in GitHub repository [kubedb/docs](https://github.com/kubedb/docs). +> Note: YAML files used in this tutorial are stored in [docs/examples/qdrant/monitoring](https://github.com/kubedb/docs/tree/{{< param "info.version" >}}/docs/examples/qdrant/monitoring) folder in GitHub repository [kubedb/docs](https://github.com/kubedb/docs). ## Find out required labels for ServiceMonitor -We need to know the labels used to select `ServiceMonitor` by a `Prometheus` CR. We are going to provide these labels in `spec.monitor.prometheus.serviceMonitor.labels` field of the `Qdrant` CR so that KubeDB creates `ServiceMonitor` object accordingly. +We need to know the labels used to select `ServiceMonitor` by `Prometheus` Operator. We are going to provide these labels in `spec.monitor.prometheus.serviceMonitor.labels` field of Qdrant CR so that KubeDB creates `ServiceMonitor` object accordingly. -At first, let's find out the available Prometheus server in our cluster: +At first, let's find out the available Prometheus server in our cluster. ```bash $ kubectl get prometheus --all-namespaces -NAMESPACE NAME AGE -monitoring prometheus 18m +NAMESPACE NAME VERSION DESIRED READY RECONCILED AVAILABLE AGE +monitoring prometheus-kube-prometheus-prometheus v2.54.1 1 1 True True 16d ``` -Now, let's view the YAML of the available Prometheus server `prometheus` in `monitoring` namespace: +> If you don't have any Prometheus server running in your cluster, deploy one following the guide specified in **Before You Begin** section. +Now, let's view the YAML of the available Prometheus server `prometheus-kube-prometheus-prometheus` in `monitoring` namespace. + +```bash +$ kubectl get prometheus -n monitoring prometheus-kube-prometheus-prometheus -oyaml +``` ```yaml -$ kubectl get prometheus -n monitoring prometheus -o yaml apiVersion: monitoring.coreos.com/v1 kind: Prometheus metadata: + annotations: + meta.helm.sh/release-name: prometheus + meta.helm.sh/release-namespace: monitoring + creationTimestamp: "2024-10-14T10:14:36Z" + generation: 1 labels: - prometheus: prometheus - name: prometheus + app: kube-prometheus-stack-prometheus + app.kubernetes.io/instance: prometheus + app.kubernetes.io/managed-by: Helm + app.kubernetes.io/part-of: kube-prometheus-stack + app.kubernetes.io/version: 65.2.0 + chart: kube-prometheus-stack-65.2.0 + heritage: Helm + release: prometheus + name: prometheus-kube-prometheus-prometheus namespace: monitoring + resourceVersion: "1004097" + uid: b7879d3e-e4bb-4425-8d78-f917561d95f7 spec: + alerting: + alertmanagers: + - apiVersion: v2 + name: prometheus-kube-prometheus-alertmanager + namespace: monitoring + pathPrefix: / + port: http-web + automountServiceAccountToken: true + enableAdminAPI: false + evaluationInterval: 30s + externalUrl: http://prometheus-kube-prometheus-prometheus.monitoring:9090 + hostNetwork: false + image: quay.io/prometheus/prometheus:v2.54.1 + listenLocal: false + logFormat: logfmt + logLevel: info + paused: false + podMonitorNamespaceSelector: {} + podMonitorSelector: + matchLabels: + release: prometheus + portName: http-web + probeNamespaceSelector: {} + probeSelector: + matchLabels: + release: prometheus replicas: 1 - resources: - requests: - memory: 400Mi - serviceAccountName: prometheus + retention: 10d + routePrefix: / + ruleNamespaceSelector: {} + ruleSelector: + matchLabels: + release: prometheus + scrapeConfigNamespaceSelector: {} + scrapeConfigSelector: + matchLabels: + release: prometheus + scrapeInterval: 30s + securityContext: + fsGroup: 2000 + runAsGroup: 2000 + runAsNonRoot: true + runAsUser: 1000 + seccompProfile: + type: RuntimeDefault + serviceAccountName: prometheus-kube-prometheus-prometheus + serviceMonitorNamespaceSelector: {} serviceMonitorSelector: matchLabels: release: prometheus + shards: 1 + tsdb: + outOfOrderTimeWindow: 0s + version: v2.54.1 + walCompression: true +status: + availableReplicas: 1 + conditions: + - lastTransitionTime: "2024-10-31T07:38:36Z" + message: "" + observedGeneration: 1 + reason: "" + status: "True" + type: Available + - lastTransitionTime: "2024-10-31T07:38:36Z" + message: "" + observedGeneration: 1 + reason: "" + status: "True" + type: Reconciled + paused: false + replicas: 1 + selector: app.kubernetes.io/instance=prometheus-kube-prometheus-prometheus,app.kubernetes.io/managed-by=prometheus-operator,app.kubernetes.io/name=prometheus,operator.prometheus.io/name=prometheus-kube-prometheus-prometheus,prometheus=prometheus-kube-prometheus-prometheus + shardStatuses: + - availableReplicas: 1 + replicas: 1 + shardID: "0" + unavailableReplicas: 0 + updatedReplicas: 1 + shards: 1 + unavailableReplicas: 0 + updatedReplicas: 1 ``` -Notice the `spec.serviceMonitorSelector` section. Here, `release: prometheus` label is used to select `ServiceMonitor` CR. So, we are going to use this label in `spec.monitor.prometheus.serviceMonitor.labels` field of the `Qdrant` CR. +Notice the `spec.serviceMonitorSelector` section. Here, `release: prometheus` label is used to select `ServiceMonitor` CR. So, we are going to use this label in `spec.monitor.prometheus.serviceMonitor.labels` field of Qdrant CR. ## Deploy Qdrant with Monitoring Enabled -At first, let's deploy a `Qdrant` database with monitoring enabled. Below is the `Qdrant` object that we are going to create: +Now, let's deploy a Qdrant cluster with monitoring enabled. Below is the Qdrant object that we are going to create. ```yaml apiVersion: kubedb.com/v1alpha2 kind: Qdrant metadata: - name: coreos-prom-qdrant + name: qdrant-monitoring namespace: demo spec: version: "1.17.0" replicas: 3 storage: - storageClassName: "standard" + storageClassName: standard accessModes: - - ReadWriteOnce + - ReadWriteOnce resources: requests: storage: 1Gi @@ -98,83 +190,177 @@ spec: agent: prometheus.io/operator prometheus: serviceMonitor: + interval: 10s labels: release: prometheus - interval: 10s deletionPolicy: WipeOut ``` Here, -- `spec.monitor.agent: prometheus.io/operator` specifies that we are going to monitor this server using Prometheus operator. -- `spec.monitor.prometheus.serviceMonitor.labels` specifies that KubeDB should create `ServiceMonitor` with these labels. -- `spec.monitor.prometheus.serviceMonitor.interval` specifies how frequently Prometheus should scrape this database. +- `monitor.agent: prometheus.io/operator` indicates that we are going to monitor this Qdrant cluster using Prometheus operator. + +- `monitor.prometheus.serviceMonitor.labels` specifies that KubeDB should create `ServiceMonitor` with these labels. + +- `monitor.prometheus.serviceMonitor.interval` indicates that the Prometheus server should scrape metrics from this database with 10 seconds interval. -Let's create the `Qdrant` CR we have shown above: +Let's create the Qdrant object that we have shown above, ```bash -$ kubectl apply -f https://github.com/kubedb/docs/raw/{{< param "info.version" >}}/docs/examples/qdrant/monitoring/coreos-prom-qdrant.yaml -qdrant.kubedb.com/coreos-prom-qdrant created +$ kubectl create -f https://github.com/kubedb/docs/raw/{{< param "info.version" >}}/docs/examples/qdrant/monitoring/qdrant-monitoring.yaml +qdrant.kubedb.com/qdrant-monitoring created ``` -Now, wait for the database to go into `Ready` state: +Now, wait for the database to go into `Ready` state. ```bash -$ kubectl get qdrant -n demo coreos-prom-qdrant -NAME VERSION STATUS AGE -coreos-prom-qdrant 1.17.0 Ready 1m +$ kubectl get qdrant -n demo qdrant-monitoring +NAME VERSION STATUS AGE +qdrant-monitoring 1.17.0 Ready 1m ``` -KubeDB will create a `ServiceMonitor` object for this `Qdrant` database: +KubeDB will create a separate stats service with name `{qdrant cr name}-stats` for monitoring purpose. + +```bash +$ kubectl get svc -n demo --selector="app.kubernetes.io/instance=qdrant-monitoring" +NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE +qdrant-monitoring ClusterIP 10.96.225.130 6333/TCP 1m +qdrant-monitoring-stats ClusterIP 10.96.147.93 6333/TCP 1m +``` + +Here, `qdrant-monitoring-stats` service has been created for monitoring purpose. + +Let's describe this stats service. + +```bash +$ kubectl describe svc -n demo qdrant-monitoring-stats +``` +```yaml +Name: qdrant-monitoring-stats +Namespace: demo +Labels: app.kubernetes.io/component=database + app.kubernetes.io/instance=qdrant-monitoring + app.kubernetes.io/managed-by=kubedb.com + app.kubernetes.io/name=qdrants.kubedb.com + kubedb.com/role=stats +Annotations: monitoring.appscode.com/agent: prometheus.io/operator +Selector: app.kubernetes.io/instance=qdrant-monitoring,app.kubernetes.io/managed-by=kubedb.com,app.kubernetes.io/name=qdrants.kubedb.com +Type: ClusterIP +Port: metrics 6333/TCP +TargetPort: metrics/TCP +Endpoints: 10.244.0.47:6333,10.244.0.48:6333,10.244.0.49:6333 +``` + +Notice the `Labels` and `Port` fields. `ServiceMonitor` will use these information to target its endpoints. + +KubeDB will also create a `ServiceMonitor` CR in `demo` namespace that select the endpoints of `qdrant-monitoring-stats` service. Verify that the `ServiceMonitor` CR has been created. ```bash $ kubectl get servicemonitor -n demo -NAME AGE -coreos-prom-qdrant 65s +NAME AGE +qdrant-monitoring-stats 1m ``` -Let's verify the `ServiceMonitor` has the labels we specified: +Let's verify that the `ServiceMonitor` has the label that we had specified in `spec.monitor` section of Qdrant CR. ```bash -$ kubectl get servicemonitor -n demo coreos-prom-qdrant -o yaml +$ kubectl get servicemonitor -n demo qdrant-monitoring-stats -o yaml +``` + +```yaml apiVersion: monitoring.coreos.com/v1 kind: ServiceMonitor metadata: + creationTimestamp: "2024-10-31T07:38:36Z" + generation: 1 labels: app.kubernetes.io/component: database - app.kubernetes.io/instance: coreos-prom-qdrant + app.kubernetes.io/instance: qdrant-monitoring app.kubernetes.io/managed-by: kubedb.com app.kubernetes.io/name: qdrants.kubedb.com release: prometheus - name: coreos-prom-qdrant + name: qdrant-monitoring-stats namespace: demo + ownerReferences: + - apiVersion: v1 + blockOwnerDeletion: true + controller: true + kind: Service + name: qdrant-monitoring-stats + uid: 99193679-301b-41fd-aae5-a732b3070d19 + resourceVersion: "1004080" + uid: 87635ad4-dfb2-4544-89af-e48b40783205 spec: endpoints: - - honorLabels: true - interval: 10s - path: /metrics - port: metrics + - honorLabels: true + interval: 10s + path: /metrics + port: metrics namespaceSelector: matchNames: - - demo + - demo selector: matchLabels: - app.kubernetes.io/instance: coreos-prom-qdrant + app.kubernetes.io/component: database + app.kubernetes.io/instance: qdrant-monitoring + app.kubernetes.io/managed-by: kubedb.com app.kubernetes.io/name: qdrants.kubedb.com + kubedb.com/role: stats ``` -Notice that the `ServiceMonitor` has the label `release: prometheus`, which will be picked up by the Prometheus server. +Notice that the `ServiceMonitor` has label `release: prometheus` that we had specified in Qdrant CR. + +Also notice that the `ServiceMonitor` has selector which match the labels we have seen in the `qdrant-monitoring-stats` service. It also, target the `metrics` port that we have seen in the stats service. + +## Verify Monitoring Metrics + +At first, let's find out the respective Prometheus pod for `prometheus-kube-prometheus-prometheus` Prometheus server. + +```bash +$ kubectl get pod -n monitoring -l=app.kubernetes.io/name=prometheus +NAME READY STATUS RESTARTS AGE +prometheus-prometheus-kube-prometheus-prometheus-0 2/2 Running 1 16d +``` -## Verify Monitoring +Prometheus server is listening to port `9090` of `prometheus-prometheus-kube-prometheus-prometheus-0` pod. We are going to use [port forwarding](https://kubernetes.io/docs/tasks/access-application-cluster/port-forward-access-application-cluster/) to access Prometheus dashboard. -Once everything is set up, you can visit the Prometheus dashboard. The `coreos-prom-qdrant` service monitor will be discovered and its metrics will be scraped. You should be able to query Qdrant metrics in Prometheus. +Run following command on a separate terminal to forward the port 9090 of `prometheus-prometheus-0` pod, + +```bash +$ kubectl port-forward -n monitoring prometheus-prometheus-kube-prometheus-prometheus-0 9090 +Forwarding from 127.0.0.1:9090 -> 9090 +Forwarding from [::1]:9090 -> 9090 +``` + +Now, we can access the dashboard at `localhost:9090`. Open [http://localhost:9090](http://localhost:9090) in your browser. You should see `metrics` endpoint of `qdrant-monitoring-stats` service as one of the targets. + +

+  Prometheus Target +

+ +Check the `endpoint` and `service` labels. It verifies that the target is our expected database. Now, you can view the collected metrics and create a graph from homepage of this Prometheus dashboard. You can also use this Prometheus server as data source for [Grafana](https://grafana.com/) and create beautiful dashboards with collected metrics. + +## Grafana Dashboards + +There are pre-built Grafana dashboards to monitor Qdrant databases managed by KubeDB: + +- KubeDB / Qdrant / Summary: Shows overall summary of Qdrant instance. +- KubeDB / Qdrant / Pod: Shows individual pod-level information. + +To use these dashboards, download them from [qdrant-dashboards](https://github.com/ops-center/grafana-dashboards/tree/master/qdrant) and import them into your Grafana instance. ## Cleaning up -To clean up the Kubernetes resources created by this tutorial, run: +To clean up the Kubernetes resources created by this tutorial, run following commands ```bash -kubectl delete qdrant -n demo coreos-prom-qdrant +kubectl delete -n demo qdrant/qdrant-monitoring kubectl delete ns demo + +helm uninstall prometheus -n monitoring kubectl delete ns monitoring -``` \ No newline at end of file +``` + +## Next Steps +- Learn about [backup and restore](/docs/guides/qdrant/backup/overview/index.md) Qdrant using KubeStash. +- Want to hack on KubeDB? Check our [contribution guidelines](/docs/CONTRIBUTING.md). diff --git a/docs/guides/qdrant/quickstart/quickstart.md b/docs/guides/qdrant/quickstart/quickstart.md index cddf728d6..df43b0b56 100644 --- a/docs/guides/qdrant/quickstart/quickstart.md +++ b/docs/guides/qdrant/quickstart/quickstart.md @@ -389,6 +389,54 @@ $ curl -H "api-key: $QDRANT_API_KEY" http://localhost:6333/collections {"result":{"collections":[{"name":"KubeDBHealthCheckCollection"}]},"status":"ok","time":0.00001235} ``` +Let's create a collection with some vector data: + +```bash +$ curl -X PUT http://localhost:6333/collections/demo_vectors \ + -H "Content-Type: application/json" \ + -H "api-key: $QDRANT_API_KEY" \ + -d '{ + "vectors": { + "size": 8, + "distance": "Cosine" + } + }' +{"result":true,"status":"ok","time":0.050803361} + +$ curl -X PUT "http://localhost:6333/collections/demo_vectors/points?wait=true" \ + -H "Content-Type: application/json" \ + -H "api-key: $QDRANT_API_KEY" \ + -d '{ + "points": [ + {"id": 1, "vector": [0.15, 0.22, 0.31, 0.44, 0.51, 0.68, 0.73, 0.89], "payload": {"label": "apple"}}, + {"id": 2, "vector": [0.12, 0.28, 0.35, 0.42, 0.53, 0.64, 0.71, 0.85], "payload": {"label": "banana"}}, + {"id": 3, "vector": [0.18, 0.21, 0.33, 0.46, 0.50, 0.66, 0.77, 0.82], "payload": {"label": "cherry"}} + ] + }' +{"result":{"operation_id":1,"status":"completed"},"status":"ok","time":0.001645376} +``` + +Now scroll through the points to verify they were stored: + +```bash +$ curl -X POST http://localhost:6333/collections/demo_vectors/points/scroll \ + -H "Content-Type: application/json" \ + -H "api-key: $QDRANT_API_KEY" \ + -d '{"limit": 5, "with_payload": true, "with_vector": false}' | jq +{ + "result": { + "points": [ + {"id": 1, "payload": {"label": "apple"}, "version": 1}, + {"id": 2, "payload": {"label": "banana"}, "version": 1}, + {"id": 3, "payload": {"label": "cherry"}, "version": 1} + ], + "next_page_offset": null + }, + "status": "ok", + "time": 0.000086921 +} +``` + ## AppBinding KubeDB creates an [AppBinding](/docs/guides/qdrant/concepts/appbinding.md) CR that holds the necessary information to connect with the database.