diff --git a/infra/qlever/qlever-k8s.yaml b/infra/qlever/qlever-k8s.yaml new file mode 100644 index 0000000..3fc463c --- /dev/null +++ b/infra/qlever/qlever-k8s.yaml @@ -0,0 +1,444 @@ +--- +# Namespace (optional - can deploy to default namespace instead) +apiVersion: v1 +kind: Namespace +metadata: + name: qlever + labels: + name: qlever + +--- +# ConfigMap for environment variables +# Translates your qlever.env file into Kubernetes config +apiVersion: v1 +kind: ConfigMap +metadata: + name: qlever-config + namespace: qlever +data: + # User/Group IDs for permission management + UID: "1000" + GID: "1000" + + # QLever configuration generation + QLEVER_GENERATE_CONFIG_FILE: "auto" + QLEVER_FILE_PATH: "/data/Qleverfile" + + # Data configuration + QLEVER_DATA_NAME: "default" + QLEVER_DATA_DESCRIPTION: "Default dataset" + + # Index settings + QLEVER_INDEX_SETTINGS_JSON: '{ "ascii-prefixes-only": false, "num-triples-per-batch": 100000 }' + QLEVER_INDEX_PARALLEL_PARSING: "true" + + # Server settings + QLEVER_SERVER_ACCESS_TOKEN: "default_7643543846_Zs6nw7yi3Z9m" + QLEVER_SERVER_HOST_NAME: "0.0.0.0" # Listen on all interfaces in k8s + QLEVER_SERVER_PORT: "7001" + QLEVER_SERVER_MEMORY_FOR_QUERIES: "5G" + QLEVER_SERVER_CACHE_MAX_SIZE: "2G" + QLEVER_SERVER_TIMEOUT: "30s" + + # Runtime + QLEVER_RUNTIME_SYSTEM: "native" + + # Entrypoint script behavior + SHOULD_INDEX: "false" + FORCE_INDEXING: "false" + SHOULD_DOWNLOAD: "true" + FORCE_DOWNLOAD: "false" + START_ADDITIONAL_ARGS: "" + STOP_ON_CALL_ENABLED: "false" + +--- +# PersistentVolumeClaim for QLever data storage +# This provides persistent storage for index data, Qleverfile, etc. +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: qlever-data-pvc + namespace: qlever +spec: + accessModes: + - ReadWriteOnce # Single pod read-write access + resources: + requests: + storage: 20Gi # Adjust based on your data size + # storageClassName: standard # Uncomment and set your storage class if needed + +--- +# ConfigMap for entrypoint script +# Mounts your custom entrypoint.sh +apiVersion: v1 +kind: ConfigMap +metadata: + name: qlever-scripts + namespace: qlever +data: + entrypoint.sh: | + #!/bin/bash + set -euo pipefail + + # Configuration from environment + SHOULD_INDEX="${SHOULD_INDEX:-false}" + FORCE_INDEXING="${FORCE_INDEXING:-false}" + SHOULD_DOWNLOAD="${SHOULD_DOWNLOAD:-true}" + FORCE_DOWNLOAD="${FORCE_DOWNLOAD:-false}" + START_ADDITIONAL_ARGS="${START_ADDITIONAL_ARGS:-}" + STOP_ON_CALL_ENABLED="${STOP_ON_CALL_ENABLED:-false}" + + echo "INFO: Indexing : should index = ${SHOULD_INDEX} ; force indexing = ${FORCE_INDEXING}" + echo "INFO: Data download : should download = ${SHOULD_DOWNLOAD} ; force download = ${FORCE_DOWNLOAD}" + echo "UID=$(id -u)" + echo "GID=$(id -g)" + + # Generate Qleverfile + /bin/sh /qlever/scripts/generate-qleverfile.sh + + cd /data + + QLEVER_FILE_PATH="${QLEVER_FILE_PATH:-/data/Qleverfile}" + + if [ ! -f "${QLEVER_FILE_PATH}" ]; then + echo "ERROR: Qleverfile not found at '${QLEVER_FILE_PATH}'" + exit 1 + fi + + echo "INFO: Qleverfile found at '${QLEVER_FILE_PATH}'" + cat "${QLEVER_FILE_PATH}" + + # Extract INPUT_FILES from Qleverfile + INPUT_FILES=$(grep "^INPUT_FILES[[:space:]]*=" "${QLEVER_FILE_PATH}" | head -n1 | sed 's/.*=[[:space:]]*//') + HAS_INPUT_FILES=$(echo "${INPUT_FILES}" | sed '/^[[:space:]]*$/d' | wc -l) + + if [ "${HAS_INPUT_FILES}" -ne 0 ]; then + echo "INFO: Found 'INPUT_FILES' in the Qleverfile" + + HAS_MISSING_INPUT_FILES="false" + for INPUT_FILE in ${INPUT_FILES}; do + if [ -f "${INPUT_FILE}" ]; then + echo "INFO: Input file found at '${INPUT_FILE}'" + else + echo "INFO: Input file not found at '${INPUT_FILE}'" + HAS_MISSING_INPUT_FILES="true" + fi + done + + if [ "${HAS_MISSING_INPUT_FILES}" = "false" ]; then + SHOULD_DOWNLOAD="false" + fi + + if [ "${SHOULD_DOWNLOAD}" = "true" ]; then + echo "INFO: Trigger download of input files…" + fi + fi + + if [ "${FORCE_DOWNLOAD}" = "true" ]; then + echo "INFO: Forcing download of input files…" + SHOULD_DOWNLOAD="true" + fi + + if grep -q "^GET_DATA_CMD" "${QLEVER_FILE_PATH}"; then + echo "INFO: Found 'GET_DATA_CMD' in the Qleverfile" + if [ "${SHOULD_DOWNLOAD}" = "true" ]; then + echo "INFO: Trigger download of data…" + qlever get-data + SHOULD_INDEX="true" + else + echo "INFO: Skipping download of data…" + fi + fi + + if [ "${SHOULD_INDEX}" = "true" ]; then + echo "INFO: Indexing is enabled" + qlever index --overwrite-existing + elif [ "${FORCE_INDEXING}" = "true" ]; then + echo "INFO: Forcing indexing" + qlever index --overwrite-existing + else + echo "INFO: Indexing is disabled" + fi + + echo "INFO: Starting QLever server..." + if [ "${STOP_ON_CALL_ENABLED}" = "true" ]; then + qlever start --run-in-foreground $START_ADDITIONAL_ARGS & + QLEVER_PID=$! + + stop_on_call & + STOP_ON_CALL_PID=$! + + wait -n $QLEVER_PID $STOP_ON_CALL_PID + + if ! kill -0 $QLEVER_PID 2>/dev/null; then + wait $QLEVER_PID + QLEVER_EXIT_CODE=$? + echo "qlever exited with code $QLEVER_EXIT_CODE" + kill $STOP_ON_CALL_PID 2>/dev/null + exit $QLEVER_EXIT_CODE + else + echo "[INFO] Stopped using stop_on_call" + qlever stop + kill $QLEVER_PID 2>/dev/null || true + wait $QLEVER_PID || true + exit 0 + fi + else + exec qlever start --run-in-foreground $START_ADDITIONAL_ARGS + fi + + generate-qleverfile.sh: | + #!/bin/sh + set -eu + + QLEVER_FILE_PATH="${QLEVER_FILE_PATH:-/data/Qleverfile}" + QLEVER_GENERATE_CONFIG_FILE="${QLEVER_GENERATE_CONFIG_FILE:-true}" + + if [ "${QLEVER_GENERATE_CONFIG_FILE}" = "auto" ]; then + if [ -f "${QLEVER_FILE_PATH}" ]; then + echo "INFO: Skipping Qleverfile generation, file already exists at '${QLEVER_FILE_PATH}'" + exit 0 + else + echo "INFO: Generating Qleverfile at '${QLEVER_FILE_PATH}'" + QLEVER_GENERATE_CONFIG_FILE="true" + fi + fi + + if [ "${QLEVER_GENERATE_CONFIG_FILE}" != "true" ]; then + echo "INFO: Skipping Qleverfile generation" + exit 0 + fi + + dirname "${QLEVER_FILE_PATH}" | xargs mkdir -p + + export QLEVER_DATA_NAME="${QLEVER_DATA_NAME:-default}" + export QLEVER_DATA_DESCRIPTION="${QLEVER_DATA_DESCRIPTION:-Default dataset}" + export QLEVER_INDEX_SETTINGS_JSON="${QLEVER_INDEX_SETTINGS_JSON:-{ \"ascii-prefixes-only\": false, \"num-triples-per-batch\": 100000 }}" + export QLEVER_INDEX_PARALLEL_PARSING="${QLEVER_INDEX_PARALLEL_PARSING:-true}" + export QLEVER_SERVER_ACCESS_TOKEN="${QLEVER_SERVER_ACCESS_TOKEN:-${QLEVER_DATA_NAME}_7643543846_Zs6nw7yi3Z9m}" + export QLEVER_SERVER_HOST_NAME="${QLEVER_SERVER_HOST_NAME:-127.0.0.1}" + export QLEVER_SERVER_PORT="${QLEVER_SERVER_PORT:-7001}" + export QLEVER_SERVER_MEMORY_FOR_QUERIES="${QLEVER_SERVER_MEMORY_FOR_QUERIES:-5G}" + export QLEVER_SERVER_CACHE_MAX_SIZE="${QLEVER_SERVER_CACHE_MAX_SIZE:-2G}" + export QLEVER_SERVER_TIMEOUT="${QLEVER_SERVER_TIMEOUT:-30s}" + export QLEVER_RUNTIME_SYSTEM="${QLEVER_RUNTIME_SYSTEM:-native}" + export QLEVER_UI_UI_CONFIG="${QLEVER_UI_UI_CONFIG:-default}" + export QLEVER_UI_UI_PORT="${QLEVER_UI_UI_PORT:-7002}" + + QLEVER_ENV_VARS="$(env | grep '^QLEVER_' | sort -u)" + + generate_config_section() { + section="$1" + section_upper="$(echo "${section}" | tr '[:lower:]' '[:upper:]')" + section_env_vars="$(echo "${QLEVER_ENV_VARS}" | grep "^QLEVER_${section_upper}_" | sed "s/^QLEVER_${section_upper}_//")" + + if [ -z "${section_env_vars}" ]; then + return + fi + + echo "[$section]" + echo "${section_env_vars}" + echo "" + } + + { + echo "# Qleverfile auto-generated at $(date)" + echo "" + generate_config_section "data" + generate_config_section "index" + generate_config_section "server" + generate_config_section "runtime" + generate_config_section "ui" + } > "${QLEVER_FILE_PATH}" + + echo "INFO: Generated Qleverfile at '${QLEVER_FILE_PATH}'" + exit 0 + +--- +# Deployment for QLever server +apiVersion: apps/v1 +kind: Deployment +metadata: + name: qlever-server + namespace: qlever + labels: + app: qlever-server +spec: + replicas: 1 # Single instance - QLever isn't designed for horizontal scaling + selector: + matchLabels: + app: qlever-server + template: + metadata: + labels: + app: qlever-server + spec: + # Init container to fix permissions (replaces qlever-init from docker-compose) + initContainers: + - name: fix-permissions + image: busybox:latest + command: + - sh + - -c + - | + echo "Fixing /data ownership to ${UID:-1000}:${GID:-1000}" + chown -R ${UID:-1000}:${GID:-1000} /data + chmod -R u+rwX,g+rwX,o+rX /data + envFrom: + - configMapRef: + name: qlever-config + volumeMounts: + - name: data + mountPath: /data + securityContext: + runAsUser: 0 # Init container runs as root to fix permissions + + containers: + - name: qlever-server + image: adfreiburg/qlever:latest + + # For Apple Silicon compatibility (uncomment if needed) + # This would need to be set via environment or overlay + # platform: linux/amd64 + + # Run as specified UID/GID + securityContext: + runAsUser: 1000 + runAsGroup: 1000 + fsGroup: 1000 + + # Working directory + workingDir: /data + + # Custom entrypoint + command: ["/bin/bash", "/qlever/scripts/entrypoint.sh"] + + # Environment variables from ConfigMap + envFrom: + - configMapRef: + name: qlever-config + + ports: + - name: qlever + containerPort: 7001 + protocol: TCP + + # Volume mounts + volumeMounts: + # Main data volume (persistent) + - name: data + mountPath: /data + + # Scripts from ConfigMap + - name: scripts + mountPath: /qlever/scripts + readOnly: true + + # Input data (you'll need to configure this based on your setup) + # Option 1: Mount from host path (for development) + # - name: input-data + # mountPath: /input + # readOnly: true + + # Option 2: Mount from another PVC or ConfigMap + # Uncomment and configure as needed + + # Resource requests and limits (adjust based on your needs) + resources: + requests: + memory: "4Gi" + cpu: "1000m" + limits: + memory: "8Gi" + cpu: "2000m" + + # Liveness probe - checks if server is responding + livenessProbe: + httpGet: + path: / # Adjust based on QLever's health endpoint + port: 7001 + initialDelaySeconds: 60 # Wait for indexing to complete + periodSeconds: 30 + timeoutSeconds: 5 + failureThreshold: 3 + + # Readiness probe - checks if server is ready to accept traffic + readinessProbe: + httpGet: + path: / # Adjust based on QLever's health endpoint + port: 7001 + initialDelaySeconds: 30 + periodSeconds: 10 + timeoutSeconds: 5 + failureThreshold: 3 + + volumes: + # Persistent data volume + - name: data + persistentVolumeClaim: + claimName: qlever-data-pvc + + # Scripts from ConfigMap + - name: scripts + configMap: + name: qlever-scripts + defaultMode: 0755 # Make scripts executable + + # Input data volume (configure based on your needs) + # Option 1: HostPath (for development/testing) + # - name: input-data + # hostPath: + # path: /path/to/your/data + # type: Directory + + # Option 2: Another PVC + # - name: input-data + # persistentVolumeClaim: + # claimName: qlever-input-data-pvc + + # Restart policy + restartPolicy: Always + +--- +# Service to expose QLever server +apiVersion: v1 +kind: Service +metadata: + name: qlever-server + namespace: qlever + labels: + app: qlever-server +spec: + type: ClusterIP # Change to LoadBalancer or NodePort if you need external access + selector: + app: qlever-server + ports: + - name: qlever + port: 7001 + targetPort: 7001 + protocol: TCP + # nodePort: 30701 # Uncomment if using NodePort + +--- +# Optional: Ingress for external access +# Uncomment and configure if you want to expose via ingress controller +# apiVersion: networking.k8s.io/v1 +# kind: Ingress +# metadata: +# name: qlever-ingress +# namespace: qlever +# annotations: +# # Add ingress controller specific annotations +# # nginx.ingress.kubernetes.io/rewrite-target: / +# spec: +# rules: +# - host: qlever.example.com # Replace with your domain +# http: +# paths: +# - path: / +# pathType: Prefix +# backend: +# service: +# name: qlever-server +# port: +# number: 7001 \ No newline at end of file