From e19e2fa3101f1657bf02e6a878a18a0ba65ea77b Mon Sep 17 00:00:00 2001 From: Masaki Yatsu Date: Sun, 12 Oct 2025 14:11:42 +0900 Subject: [PATCH] feat(qdrant): install Qdrant --- justfile | 1 + qdrant/.gitignore | 2 + qdrant/justfile | 196 ++++++++++++++++++ ...ant-api-keys-external-secret.gomplate.yaml | 27 +++ qdrant/qdrant-values.gomplate.yaml | 27 +++ 5 files changed, 253 insertions(+) create mode 100644 qdrant/.gitignore create mode 100644 qdrant/justfile create mode 100644 qdrant/qdrant-api-keys-external-secret.gomplate.yaml create mode 100644 qdrant/qdrant-values.gomplate.yaml diff --git a/justfile b/justfile index 5d8c70a..8067159 100644 --- a/justfile +++ b/justfile @@ -22,6 +22,7 @@ mod metabase mod minio mod oauth2-proxy mod postgres +mod qdrant mod utils mod vault diff --git a/qdrant/.gitignore b/qdrant/.gitignore new file mode 100644 index 0000000..52dbfdf --- /dev/null +++ b/qdrant/.gitignore @@ -0,0 +1,2 @@ +qdrant-values.yaml +qdrant-api-keys-external-secret.yaml diff --git a/qdrant/justfile b/qdrant/justfile new file mode 100644 index 0000000..868659b --- /dev/null +++ b/qdrant/justfile @@ -0,0 +1,196 @@ +set fallback := true + +export QDRANT_NAMESPACE := env("QDRANT_NAMESPACE", "qdrant") +export QDRANT_VERSION := env("QDRANT_VERSION", "1.15.5") +export QDRANT_HOST := env("QDRANT_HOST", "") +export EXTERNAL_SECRETS_NAMESPACE := env("EXTERNAL_SECRETS_NAMESPACE", "external-secrets") + +[private] +default: + @just --list --unsorted --list-submodules + +# Add Helm repository +add-helm-repo: + helm repo add qdrant https://qdrant.github.io/qdrant-helm + helm repo update + +# Remove Helm repository +remove-helm-repo: + helm repo remove qdrant + +# Create Qdrant namespace +create-namespace: + @kubectl get namespace ${QDRANT_NAMESPACE} &>/dev/null || \ + kubectl create namespace ${QDRANT_NAMESPACE} + +# Delete Qdrant namespace +delete-namespace: + @kubectl delete namespace ${QDRANT_NAMESPACE} --ignore-not-found + +# Create Qdrant API keys secret +create-api-keys: + #!/bin/bash + set -euo pipefail + echo "Setting up Qdrant API keys..." + + # Generate API keys + API_KEY=$(just utils::random-password) + READONLY_API_KEY=$(just utils::random-password) + + if helm status external-secrets -n ${EXTERNAL_SECRETS_NAMESPACE} &>/dev/null; then + echo "External Secrets available. Storing API keys in Vault and creating ExternalSecret..." + just vault::put qdrant/api-keys api_key="$API_KEY" readonly_api_key="$READONLY_API_KEY" + gomplate -f qdrant-api-keys-external-secret.gomplate.yaml -o qdrant-api-keys-external-secret.yaml + kubectl apply -f qdrant-api-keys-external-secret.yaml + echo "Waiting for API keys secret to be ready..." + kubectl wait --for=condition=Ready externalsecret/qdrant-api-keys-external-secret \ + -n ${QDRANT_NAMESPACE} --timeout=60s + else + echo "External Secrets not available. Creating Kubernetes Secret directly..." + kubectl delete secret qdrant-api-keys -n ${QDRANT_NAMESPACE} --ignore-not-found + kubectl create secret generic qdrant-api-keys -n ${QDRANT_NAMESPACE} \ + --from-literal=api_key="$API_KEY" \ + --from-literal=readonly_api_key="$READONLY_API_KEY" + echo "API keys secret created directly in Kubernetes" + fi + echo "Qdrant API keys setup completed" + +# Delete Qdrant API keys secret +delete-api-keys-secret: + @kubectl delete secret qdrant-api-keys -n ${QDRANT_NAMESPACE} --ignore-not-found + @kubectl delete externalsecret qdrant-api-keys-external-secret -n ${QDRANT_NAMESPACE} --ignore-not-found + +# Install Qdrant +install: + #!/bin/bash + set -euo pipefail + just add-helm-repo + just create-namespace + just create-api-keys + while [ -z "${QDRANT_HOST}" ]; do + QDRANT_HOST=$( + gum input --prompt="Qdrant host (FQDN): " --width=100 \ + --placeholder="e.g., qdrant.example.com" + ) + done + gomplate -f qdrant-values.gomplate.yaml -o qdrant-values.yaml + helm upgrade --install qdrant qdrant/qdrant \ + --version ${QDRANT_VERSION} -n ${QDRANT_NAMESPACE} --create-namespace --wait \ + -f qdrant-values.yaml + +# Uninstall Qdrant +uninstall: + helm uninstall qdrant -n ${QDRANT_NAMESPACE} --wait --ignore-not-found + just delete-api-keys-secret + just delete-namespace + +# Get API key +get-api-key: + @kubectl get secret qdrant-api-keys -n ${QDRANT_NAMESPACE} \ + -o jsonpath="{.data.api_key}" | base64 -d + @echo + +# Get readonly API key +get-readonly-api-key: + @kubectl get secret qdrant-api-keys -n ${QDRANT_NAMESPACE} \ + -o jsonpath="{.data.readonly_api_key}" | base64 -d + @echo + +# Check if telepresence is connected +[private] +check-telepresence: + #!/bin/bash + set -euo pipefail + if ! command -v telepresence &>/dev/null; then + echo "Error: telepresence is not installed" >&2 + exit 1 + fi + if ! telepresence status &>/dev/null; then + echo "Error: telepresence is not connected" >&2 + echo "Please run: telepresence connect" >&2 + exit 1 + fi + +# Get Qdrant service URL +[private] +get-service-url: + @echo "http://qdrant.${QDRANT_NAMESPACE}.svc.cluster.local:6333" + +# Check Qdrant health +health-check: + #!/bin/bash + set -euo pipefail + just check-telepresence + API_KEY=$(just get-api-key) + QDRANT_URL=$(just get-service-url) + echo "Checking Qdrant health at ${QDRANT_URL}..." + curl -s -H "api-key: ${API_KEY}" "${QDRANT_URL}/healthz" + echo + +# Test Qdrant with basic vector operations +test: + #!/bin/bash + set -euo pipefail + just check-telepresence + API_KEY=$(just get-api-key) + QDRANT_URL=$(just get-service-url) + COLLECTION_NAME="test_collection_$(date +%s)" + + echo "Testing Qdrant at ${QDRANT_URL}" + echo "Using collection: ${COLLECTION_NAME}" + echo + + echo "1. Creating collection..." + curl -s -X PUT "${QDRANT_URL}/collections/${COLLECTION_NAME}" \ + -H "api-key: ${API_KEY}" \ + -H "Content-Type: application/json" \ + -d '{ + "vectors": { + "size": 4, + "distance": "Cosine" + } + }' | jq . + echo + + echo "2. Adding test points..." + curl -s -X PUT "${QDRANT_URL}/collections/${COLLECTION_NAME}/points" \ + -H "api-key: ${API_KEY}" \ + -H "Content-Type: application/json" \ + -d '{ + "points": [ + {"id": 1, "vector": [0.05, 0.61, 0.76, 0.74], "payload": {"city": "Berlin"}}, + {"id": 2, "vector": [0.19, 0.81, 0.75, 0.11], "payload": {"city": "London"}}, + {"id": 3, "vector": [0.36, 0.55, 0.47, 0.94], "payload": {"city": "Tokyo"}} + ] + }' | jq . + echo + + echo "3. Searching for similar vectors..." + curl -s -X POST "${QDRANT_URL}/collections/${COLLECTION_NAME}/points/search" \ + -H "api-key: ${API_KEY}" \ + -H "Content-Type: application/json" \ + -d '{ + "vector": [0.2, 0.8, 0.7, 0.1], + "limit": 3 + }' | jq . + echo + + echo "4. Deleting test collection..." + curl -s -X DELETE "${QDRANT_URL}/collections/${COLLECTION_NAME}" \ + -H "api-key: ${API_KEY}" | jq . + echo + + echo "Test completed successfully!" + +# Clean up Qdrant resources +cleanup: + #!/bin/bash + set -euo pipefail + echo "This will delete all Qdrant resources and secrets." + if gum confirm "Are you sure you want to proceed?"; then + echo "Cleaning up Qdrant resources..." + just vault::delete qdrant/api-keys || true + echo "Cleanup completed" + else + echo "Cleanup cancelled" + fi diff --git a/qdrant/qdrant-api-keys-external-secret.gomplate.yaml b/qdrant/qdrant-api-keys-external-secret.gomplate.yaml new file mode 100644 index 0000000..8e850ed --- /dev/null +++ b/qdrant/qdrant-api-keys-external-secret.gomplate.yaml @@ -0,0 +1,27 @@ +apiVersion: external-secrets.io/v1 +kind: ExternalSecret +metadata: + name: qdrant-api-keys-external-secret + namespace: {{ .Env.QDRANT_NAMESPACE }} +spec: + refreshInterval: 1h + secretStoreRef: + name: vault-secret-store + kind: ClusterSecretStore + target: + name: qdrant-api-keys + creationPolicy: Owner + template: + type: Opaque + data: + api_key: "{{ `{{ .api_key }}` }}" + readonly_api_key: "{{ `{{ .readonly_api_key }}` }}" + data: + - secretKey: api_key + remoteRef: + key: qdrant/api-keys + property: api_key + - secretKey: readonly_api_key + remoteRef: + key: qdrant/api-keys + property: readonly_api_key diff --git a/qdrant/qdrant-values.gomplate.yaml b/qdrant/qdrant-values.gomplate.yaml new file mode 100644 index 0000000..d378787 --- /dev/null +++ b/qdrant/qdrant-values.gomplate.yaml @@ -0,0 +1,27 @@ +apiKey: + valueFrom: + secretKeyRef: + name: qdrant-api-keys + key: api_key + +readOnlyApiKey: + valueFrom: + secretKeyRef: + name: qdrant-api-keys + key: readonly_api_key + +ingress: + enabled: true + annotations: + kubernetes.io/ingress.class: traefik + traefik.ingress.kubernetes.io/router.entrypoints: websecure + ingressClassName: traefik + hosts: + - host: {{ .Env.QDRANT_HOST }} + paths: + - path: / + pathType: Prefix + servicePort: 6333 + tls: + - hosts: + - {{ .Env.QDRANT_HOST }}