diff --git a/backend/Dockerfile.background b/backend/Dockerfile.background index 5cc26d903b34..b78fbe3a612f 100644 --- a/backend/Dockerfile.background +++ b/backend/Dockerfile.background @@ -1,7 +1,7 @@ FROM python:3.11-slim-bullseye RUN apt-get update \ - && apt-get install -y git cmake pkg-config libprotobuf-c-dev protobuf-compiler \ + && apt-get install -y git cmake pkg-config libprotobuf-c-dev protobuf-compiler \ libprotobuf-dev libgoogle-perftools-dev libpq-dev build-essential cron curl \ supervisor \ && rm -rf /var/lib/apt/lists/* diff --git a/backend/danswer/datastores/interfaces.py b/backend/danswer/datastores/interfaces.py index a393c5699686..b4a290c6215f 100644 --- a/backend/danswer/datastores/interfaces.py +++ b/backend/danswer/datastores/interfaces.py @@ -1,6 +1,7 @@ import abc from typing import Generic from typing import TypeVar +from uuid import UUID from danswer.chunking.models import BaseChunk from danswer.chunking.models import EmbeddedIndexChunk @@ -14,7 +15,7 @@ IndexFilter = dict[str, str | list[str] | None] class DocumentIndex(Generic[T], abc.ABC): @abc.abstractmethod - def index(self, chunks: list[T], user_id: int | None) -> bool: + def index(self, chunks: list[T], user_id: UUID | None) -> bool: raise NotImplementedError @@ -23,7 +24,7 @@ class VectorIndex(DocumentIndex[EmbeddedIndexChunk], abc.ABC): def semantic_retrieval( self, query: str, - user_id: int | None, + user_id: UUID | None, filters: list[IndexFilter] | None, num_to_retrieve: int, ) -> list[InferenceChunk]: @@ -35,7 +36,7 @@ class KeywordIndex(DocumentIndex[IndexChunk], abc.ABC): def keyword_search( self, query: str, - user_id: int | None, + user_id: UUID | None, filters: list[IndexFilter] | None, num_to_retrieve: int, ) -> list[InferenceChunk]: diff --git a/backend/danswer/datastores/qdrant/indexing.py b/backend/danswer/datastores/qdrant/indexing.py index 0edd790bf798..a421347cf26c 100644 --- a/backend/danswer/datastores/qdrant/indexing.py +++ b/backend/danswer/datastores/qdrant/indexing.py @@ -1,4 +1,5 @@ from functools import partial +from uuid import UUID from danswer.chunking.models import EmbeddedIndexChunk from danswer.configs.constants import ALLOWED_GROUPS @@ -86,7 +87,7 @@ def delete_qdrant_doc_chunks( def index_qdrant_chunks( chunks: list[EmbeddedIndexChunk], - user_id: int | None, + user_id: UUID | None, collection: str, client: QdrantClient | None = None, batch_upsert: bool = True, diff --git a/backend/danswer/datastores/qdrant/store.py b/backend/danswer/datastores/qdrant/store.py index 333e67906e90..cbf017064d8f 100644 --- a/backend/danswer/datastores/qdrant/store.py +++ b/backend/danswer/datastores/qdrant/store.py @@ -1,8 +1,7 @@ -import uuid +from uuid import UUID from danswer.chunking.models import EmbeddedIndexChunk from danswer.chunking.models import InferenceChunk -from danswer.configs.app_configs import NUM_RERANKED_RESULTS from danswer.configs.app_configs import NUM_RETURNED_HITS from danswer.configs.app_configs import QDRANT_DEFAULT_COLLECTION from danswer.configs.constants import ALLOWED_USERS @@ -27,7 +26,7 @@ logger = setup_logger() def _build_qdrant_filters( - user_id: int | None, filters: list[IndexFilter] | None + user_id: UUID | None, filters: list[IndexFilter] | None ) -> list[FieldCondition]: filter_conditions: list[FieldCondition] = [] # Permissions filter @@ -78,7 +77,7 @@ class QdrantIndex(VectorIndex): self.collection = collection self.client = get_qdrant_client() - def index(self, chunks: list[EmbeddedIndexChunk], user_id: int | None) -> bool: + def index(self, chunks: list[EmbeddedIndexChunk], user_id: UUID | None) -> bool: return index_qdrant_chunks( chunks=chunks, user_id=user_id, @@ -90,7 +89,7 @@ class QdrantIndex(VectorIndex): def semantic_retrieval( self, query: str, - user_id: int | None, + user_id: UUID | None, filters: list[IndexFilter] | None, num_to_retrieve: int = NUM_RETURNED_HITS, page_size: int = NUM_RETURNED_HITS, @@ -106,7 +105,7 @@ class QdrantIndex(VectorIndex): page_offset = 0 found_inference_chunks: list[InferenceChunk] = [] - found_chunk_uuids: set[uuid.UUID] = set() + found_chunk_uuids: set[UUID] = set() while len(found_inference_chunks) < num_to_retrieve: try: hits = self.client.search( diff --git a/backend/danswer/datastores/typesense/store.py b/backend/danswer/datastores/typesense/store.py index 707fe5706d5e..244f28f04a0a 100644 --- a/backend/danswer/datastores/typesense/store.py +++ b/backend/danswer/datastores/typesense/store.py @@ -1,6 +1,7 @@ import json from functools import partial from typing import Any +from uuid import UUID import typesense # type: ignore from danswer.chunking.models import EmbeddedIndexChunk @@ -101,7 +102,7 @@ def delete_typesense_doc_chunks( def index_typesense_chunks( chunks: list[IndexChunk | EmbeddedIndexChunk], - user_id: int | None, + user_id: UUID | None, collection: str, client: typesense.Client | None = None, batch_upsert: bool = True, @@ -171,7 +172,7 @@ def index_typesense_chunks( def _build_typesense_filters( - user_id: int | None, filters: list[IndexFilter] | None + user_id: UUID | None, filters: list[IndexFilter] | None ) -> str: filter_str = "" @@ -205,7 +206,7 @@ class TypesenseIndex(KeywordIndex): self.collection = collection self.ts_client = get_typesense_client() - def index(self, chunks: list[IndexChunk], user_id: int | None) -> bool: + def index(self, chunks: list[IndexChunk], user_id: UUID | None) -> bool: return index_typesense_chunks( chunks=chunks, user_id=user_id, @@ -216,7 +217,7 @@ class TypesenseIndex(KeywordIndex): def keyword_search( self, query: str, - user_id: int | None, + user_id: UUID | None, filters: list[IndexFilter] | None, num_to_retrieve: int, ) -> list[InferenceChunk]: diff --git a/backend/danswer/db/credentials.py b/backend/danswer/db/credentials.py index 8aadf7c2ab3d..5bfd4b507413 100644 --- a/backend/danswer/db/credentials.py +++ b/backend/danswer/db/credentials.py @@ -63,7 +63,7 @@ def create_credential( ) -> ObjectCreationIdResponse: credential = Credential( credential_json=credential_data.credential_json, - user_id=int(user.id) if user else None, + user_id=user.id if user else None, public_doc=credential_data.public_doc, ) db_session.add(credential) @@ -83,7 +83,7 @@ def update_credential( return None credential.credential_json = credential_data.credential_json - credential.user_id = int(user.id) if user is not None else None + credential.user_id = user.id if user is not None else None credential.public_doc = credential_data.public_doc db_session.commit() diff --git a/backend/danswer/db/models.py b/backend/danswer/db/models.py index 39771e2d49fd..ec37054152bf 100644 --- a/backend/danswer/db/models.py +++ b/backend/danswer/db/models.py @@ -2,6 +2,7 @@ import datetime from enum import Enum as PyEnum from typing import Any from typing import List +from uuid import UUID from danswer.auth.schemas import UserRole from danswer.configs.constants import DocumentSource @@ -105,7 +106,7 @@ class Credential(Base): id: Mapped[int] = mapped_column(primary_key=True) credential_json: Mapped[dict[str, Any]] = mapped_column(postgresql.JSONB()) - user_id: Mapped[int | None] = mapped_column(ForeignKey("user.id"), nullable=True) + user_id: Mapped[UUID | None] = mapped_column(ForeignKey("user.id"), nullable=True) public_doc: Mapped[bool] = mapped_column(Boolean, default=False) time_created: Mapped[datetime.datetime] = mapped_column( DateTime(timezone=True), server_default=func.now() diff --git a/backend/danswer/search/keyword_search.py b/backend/danswer/search/keyword_search.py index 7e8ba4fa40c4..fc49d8ba3078 100644 --- a/backend/danswer/search/keyword_search.py +++ b/backend/danswer/search/keyword_search.py @@ -1,4 +1,5 @@ import json +from uuid import UUID from danswer.chunking.models import InferenceChunk from danswer.configs.app_configs import NUM_RETURNED_HITS @@ -34,7 +35,7 @@ def query_processing(query: str) -> str: @log_function_time() def retrieve_keyword_documents( query: str, - user_id: int | None, + user_id: UUID | None, filters: list[IndexFilter] | None, datastore: KeywordIndex, num_hits: int = NUM_RETURNED_HITS, diff --git a/backend/danswer/search/semantic_search.py b/backend/danswer/search/semantic_search.py index bf074d17f443..8bc2486fc881 100644 --- a/backend/danswer/search/semantic_search.py +++ b/backend/danswer/search/semantic_search.py @@ -1,4 +1,5 @@ import json +from uuid import UUID import numpy from danswer.chunking.models import EmbeddedIndexChunk @@ -58,7 +59,7 @@ def semantic_reranking( @log_function_time() def retrieve_ranked_documents( query: str, - user_id: int | None, + user_id: UUID | None, filters: list[IndexFilter] | None, datastore: VectorIndex, num_hits: int = NUM_RETURNED_HITS, diff --git a/backend/danswer/server/models.py b/backend/danswer/server/models.py index 265c5800a02a..6f25b05c04a1 100644 --- a/backend/danswer/server/models.py +++ b/backend/danswer/server/models.py @@ -4,6 +4,7 @@ from typing import Generic from typing import Literal from typing import Optional from typing import TypeVar +from uuid import UUID from danswer.configs.constants import DocumentSource from danswer.connectors.models import InputType @@ -171,7 +172,7 @@ class CredentialBase(BaseModel): class CredentialSnapshot(CredentialBase): id: int - user_id: int | None + user_id: UUID | None time_created: datetime time_updated: datetime diff --git a/backend/danswer/server/search_backend.py b/backend/danswer/server/search_backend.py index 010b5b013530..487bfe5af4c1 100644 --- a/backend/danswer/server/search_backend.py +++ b/backend/danswer/server/search_backend.py @@ -49,7 +49,7 @@ def semantic_search( filters = question.filters logger.info(f"Received semantic search query: {query}") - user_id = None if user is None else int(user.id) + user_id = None if user is None else user.id ranked_chunks, unranked_chunks = retrieve_ranked_documents( query, user_id, filters, QdrantIndex(collection) ) @@ -71,7 +71,7 @@ def keyword_search( filters = question.filters logger.info(f"Received keyword search query: {query}") - user_id = None if user is None else int(user.id) + user_id = None if user is None else user.id ranked_chunks = retrieve_keyword_documents( query, user_id, filters, TypesenseIndex(collection) ) @@ -99,7 +99,7 @@ def direct_qa( if use_keyword is None: use_keyword = predicted_search == SearchType.KEYWORD - user_id = None if user is None else int(user.id) + user_id = None if user is None else user.id if use_keyword: ranked_chunks: list[InferenceChunk] | None = retrieve_keyword_documents( query, user_id, filters, TypesenseIndex(collection) @@ -165,7 +165,7 @@ def stream_direct_qa( if use_keyword is None: use_keyword = predicted_search == SearchType.KEYWORD - user_id = None if user is None else int(user.id) + user_id = None if user is None else user.id if use_keyword: ranked_chunks: list[InferenceChunk] | None = retrieve_keyword_documents( query, user_id, filters, TypesenseIndex(collection) diff --git a/backend/danswer/utils/indexing_pipeline.py b/backend/danswer/utils/indexing_pipeline.py index 4c1418c5cb7e..9bb93afe4915 100644 --- a/backend/danswer/utils/indexing_pipeline.py +++ b/backend/danswer/utils/indexing_pipeline.py @@ -1,6 +1,7 @@ from functools import partial from itertools import chain from typing import Protocol +from uuid import UUID from danswer.chunking.chunk import Chunker from danswer.chunking.chunk import DefaultChunker @@ -16,7 +17,7 @@ from danswer.search.semantic_search import DefaultEmbedder class IndexingPipelineProtocol(Protocol): def __call__( - self, documents: list[Document], user_id: int | None + self, documents: list[Document], user_id: UUID | None ) -> list[EmbeddedIndexChunk]: ... @@ -28,7 +29,7 @@ def _indexing_pipeline( vector_index: VectorIndex, keyword_index: KeywordIndex, documents: list[Document], - user_id: int | None, + user_id: UUID | None, ) -> list[EmbeddedIndexChunk]: # TODO: make entire indexing pipeline async to not block the entire process # when running on async endpoints diff --git a/backend/supervisord.conf b/backend/supervisord.conf index 0ae893fe0be8..8bdb803f6f26 100644 --- a/backend/supervisord.conf +++ b/backend/supervisord.conf @@ -1,16 +1,18 @@ [supervisord] nodaemon=true -logfile=/dev/null +logfile=/dev/stdout logfile_maxbytes=0 [program:indexing] command=python danswer/background/update.py -stdout_logfile=/var/log/supervisor/update.log +stdout_logfile=/var/log/update.log redirect_stderr=true +stdout_logfile_maxbytes=52428800 autorestart=true [program:file_deletion] command=python danswer/background/file_deletion.py -stdout_logfile=/var/log/supervisor/file_deletion.log +stdout_logfile=/var/log/file_deletion.log redirect_stderr=true +stdout_logfile_maxbytes=52428800 autorestart=true diff --git a/deployment/.gitignore b/deployment/.gitignore index 81a38be68dc7..ddc4da4ba4bf 100644 --- a/deployment/.gitignore +++ b/deployment/.gitignore @@ -1 +1,2 @@ .env* +secrets.yaml diff --git a/deployment/README.md b/deployment/README.md index fb4556abfa2e..47e517e4519f 100644 --- a/deployment/README.md +++ b/deployment/README.md @@ -1,32 +1,77 @@ -This serves as an example for how to deploy everything on a single machine. This is -not optimal, but can get you started easily and cheaply. To run: +# Deploying Danswer +The two options provided here are the easiest ways to get Danswer up and running. + +- Docker Compose is simpler and default values are already preset to run right out of the box with a single command. +As everything is running on a single machine, this may not be as scalable depending on your hardware, traffic and needs. + +- Kubernetes deployment is also provided. Depending on your existing infrastructure, this may be more suitable for +production deployment but there are a few caveats. + - User auth is turned on by default for Kubernetes (with the assumption this is for production use) + so you must either update the deployments to turn off user auth or provide the values shown in the example + secrets.yaml file. + - The example provided assumes a blank slate for existing Kubernetes deployments/services. You may need to adjust the + deployments or services according to your setup. This may require existing Kubernetes knowledge or additional + setup time. + +All the features of Danswer are fully available regardless of the deployment option. + +For information on setting up connectors, check out https://docs.danswer.dev/connectors/overview -1. Run one of the docker compose commands below depending on your environment: - - For Local: - - `docker compose -f docker-compose.dev.yml -p danswer-stack up -d --build` - - This will start Web/API servers, Postgres (backend DB), Qdrant (vector DB), and the background indexing job. - - Downloading packages/requirements may take 20+ minutes depending on your internet connection. +## Docker Compose: +Docker Compose provides the easiest way to get Danswer up and running. + +Requirements: Docker and docker compose + +1. To run Danswer, navigate to `docker_compose` directory and run the following: + - `docker compose -f docker-compose.dev.yml -p danswer-stack up -d --build` + - This will start Web/API servers, Postgres (backend DB), Qdrant (vector DB), and the background indexing job. + - Downloading packages/requirements may take 20+ minutes depending on your internet connection and whether it needs + to install packages for GPU. -2. To shut down the deployment run (use stop to stop containers, down to remove containers): - - For Local: - - `docker compose -f docker-compose.dev.yml -p danswer-stack stop` +2. To shut down the deployment, run: + - To stop the containers: `docker compose -f docker-compose.dev.yml -p danswer-stack stop` + - To delete the containers: `docker compose -f docker-compose.dev.yml -p danswer-stack down` -3. To completely remove Danswer (**WARNING, this will also erase your indexed data and all users**) run: - - For Local: - - `docker compose -f docker-compose.dev.yml -p danswer-stack down -v` +3. To completely remove Danswer run: + - **WARNING, this will also erase your indexed data and users** + - `docker compose -f docker-compose.dev.yml -p danswer-stack down -v` -Additional steps for setting up for Prod: +Additional steps for user auth and https if you do want to use Docker Compose for production: 1. Set up a `.env` file in this directory with relevant environment variables. - Refer to env.dev.template and env.prod.template - + - To turn on user auth, set: + - ENABLE_OAUTH=True + - GOOGLE_OAUTH_CLIENT_ID=\ + - GOOGLE_OAUTH_CLIENT_SECRET=\ + - Refer to https://developers.google.com/identity/gsi/web/guides/get-google-api-clientid 2. Set up https: - Set up a `.env.nginx` file in this directory based on `env.nginx.template`. - `chmod +x init-letsencrypt.sh` + `./init-letsencrypt.sh` to set up https certificate. 3. Follow the above steps but replacing dev with prod. + + +## Kubernetes: +Depending on your deployment needs Kubernetes may be more suitable. The yamls provided will work out of the box but the +intent is for you to customize the deployment to fit your own needs. There is no data replication or auto-scaling built +in for the provided example. + +Requirements: a Kubernetes cluster and kubectl + +**NOTE: This setup does not explicitly enable https, the assumption is you would have this already set up for your +prod cluster** + +1. To run Danswer, navigate to `kubernetes` directory and run the following: + - `kubectl apply -f .` + +2. To remove Danswer, run: + - **WARNING, this will also erase your indexed data and users**' + - `kubectl delete -f .` + - To not delete the persistent volumes (Document indexes and Users), specify the specific `.yaml` files instead of + `.` without specifying delete on persistent-volumes.yaml. \ No newline at end of file diff --git a/deployment/data/nginx/app.conf.template b/deployment/data/nginx/app.conf.template index be255a32761a..6ceaa7f11293 100644 --- a/deployment/data/nginx/app.conf.template +++ b/deployment/data/nginx/app.conf.template @@ -1,4 +1,4 @@ -upstream app_server { +upstream api_server { # fail_timeout=0 means we always retry an upstream even if it failed # to return a good HTTP response @@ -35,7 +35,7 @@ server { # we don't want nginx trying to do something clever with # redirects, we set the Host: header above already. proxy_redirect off; - proxy_pass http://app_server; + proxy_pass http://api_server; } location / { diff --git a/deployment/data/nginx/app.conf.template.dev b/deployment/data/nginx/app.conf.template.dev index ad4a3d66937f..2abebb33cee0 100644 --- a/deployment/data/nginx/app.conf.template.dev +++ b/deployment/data/nginx/app.conf.template.dev @@ -1,4 +1,4 @@ -upstream app_server { +upstream api_server { # fail_timeout=0 means we always retry an upstream even if it failed # to return a good HTTP response @@ -35,7 +35,7 @@ server { # we don't want nginx trying to do something clever with # redirects, we set the Host: header above already. proxy_redirect off; - proxy_pass http://app_server; + proxy_pass http://api_server; } location / { diff --git a/deployment/.env b/deployment/docker_compose/.env similarity index 100% rename from deployment/.env rename to deployment/docker_compose/.env diff --git a/deployment/docker-compose.dev.yml b/deployment/docker_compose/docker-compose.dev.yml similarity index 95% rename from deployment/docker-compose.dev.yml rename to deployment/docker_compose/docker-compose.dev.yml index c48e5cfd900c..92f7fa857fc3 100644 --- a/deployment/docker-compose.dev.yml +++ b/deployment/docker_compose/docker-compose.dev.yml @@ -2,7 +2,7 @@ version: '3' services: api_server: build: - context: ../backend + context: ../../backend dockerfile: Dockerfile depends_on: - relational_db @@ -24,7 +24,7 @@ services: - file_connector_tmp_storage:/home/file_connector_storage background: build: - context: ../backend + context: ../../backend dockerfile: Dockerfile.background depends_on: - relational_db @@ -42,7 +42,7 @@ services: - file_connector_tmp_storage:/home/file_connector_storage web_server: build: - context: ../web + context: ../../web dockerfile: Dockerfile depends_on: - api_server @@ -97,7 +97,7 @@ services: - "80:80" - "3000:80" # allow for localhost:3000 usage, since that is the norm volumes: - - ./data/nginx:/etc/nginx/conf.d + - ../data/nginx:/etc/nginx/conf.d command: > /bin/sh -c "envsubst '$$\{DOMAIN\}' < /etc/nginx/conf.d/app.conf.template.dev > /etc/nginx/conf.d/app.conf && while :; do sleep 6h & wait $${!}; nginx -s reload; done & nginx -g \"daemon off;\"" diff --git a/deployment/docker-compose.prod.yml b/deployment/docker_compose/docker-compose.prod.yml similarity index 89% rename from deployment/docker-compose.prod.yml rename to deployment/docker_compose/docker-compose.prod.yml index 41ddf5383ce9..ef7b840f7b2c 100644 --- a/deployment/docker-compose.prod.yml +++ b/deployment/docker_compose/docker-compose.prod.yml @@ -2,7 +2,7 @@ version: '3' services: api_server: build: - context: ../backend + context: ../../backend dockerfile: Dockerfile depends_on: - relational_db @@ -21,7 +21,7 @@ services: - file_connector_tmp_storage:/home/file_connector_storage background: build: - context: ../backend + context: ../../backend dockerfile: Dockerfile.background depends_on: - relational_db @@ -37,7 +37,7 @@ services: - file_connector_tmp_storage:/home/file_connector_storage web_server: build: - context: ../web + context: ../../web dockerfile: Dockerfile depends_on: - api_server @@ -82,9 +82,9 @@ services: - "80:80" - "443:443" volumes: - - ./data/nginx:/etc/nginx/conf.d - - ./data/certbot/conf:/etc/letsencrypt - - ./data/certbot/www:/var/www/certbot + - ../data/nginx:/etc/nginx/conf.d + - ../data/certbot/conf:/etc/letsencrypt + - ../data/certbot/www:/var/www/certbot command: > /bin/sh -c "envsubst '$$\{DOMAIN\}' < /etc/nginx/conf.d/app.conf.template > /etc/nginx/conf.d/app.conf && while :; do sleep 6h & wait $${!}; nginx -s reload; done & nginx -g \"daemon off;\"" @@ -95,8 +95,8 @@ services: image: certbot/certbot restart: always volumes: - - ./data/certbot/conf:/etc/letsencrypt - - ./data/certbot/www:/var/www/certbot + - ../data/certbot/conf:/etc/letsencrypt + - ../data/certbot/www:/var/www/certbot entrypoint: "/bin/sh -c 'trap exit TERM; while :; do certbot renew; sleep 12h & wait $${!}; done;'" volumes: local_dynamic_storage: diff --git a/deployment/env.dev.template b/deployment/docker_compose/env.dev.template similarity index 100% rename from deployment/env.dev.template rename to deployment/docker_compose/env.dev.template diff --git a/deployment/env.nginx.template b/deployment/docker_compose/env.nginx.template similarity index 100% rename from deployment/env.nginx.template rename to deployment/docker_compose/env.nginx.template diff --git a/deployment/env.prod.template b/deployment/docker_compose/env.prod.template similarity index 100% rename from deployment/env.prod.template rename to deployment/docker_compose/env.prod.template diff --git a/deployment/init-letsencrypt.sh b/deployment/docker_compose/init-letsencrypt.sh similarity index 100% rename from deployment/init-letsencrypt.sh rename to deployment/docker_compose/init-letsencrypt.sh diff --git a/deployment/kubernetes/api_server-service-deployment.yaml b/deployment/kubernetes/api_server-service-deployment.yaml new file mode 100644 index 000000000000..922b1e791186 --- /dev/null +++ b/deployment/kubernetes/api_server-service-deployment.yaml @@ -0,0 +1,68 @@ +apiVersion: v1 +kind: Service +metadata: + name: api-server-service +spec: + selector: + app: api-server + ports: + - name: api-server-port + protocol: TCP + port: 80 + targetPort: 8080 + type: ClusterIP +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: api-server-deployment +spec: + replicas: 1 + selector: + matchLabels: + app: api-server + template: + metadata: + labels: + app: api-server + spec: + containers: + - name: api-server + image: danswer/danswer-api-server:latest + imagePullPolicy: IfNotPresent + ports: + - containerPort: 8080 + env: + - name: POSTGRES_HOST + value: relational-db-service + - name: QDRANT_HOST + value: vector-db-service + - name: TYPESENSE_HOST + value: search-engine-service + - name: TYPESENSE_API_KEY + valueFrom: + secretKeyRef: + name: danswer-secrets + key: typesense_api_key + - name: GOOGLE_OAUTH_CLIENT_ID + valueFrom: + secretKeyRef: + name: danswer-secrets + key: google_oauth_client_id + - name: GOOGLE_OAUTH_CLIENT_SECRET + valueFrom: + secretKeyRef: + name: danswer-secrets + key: google_oauth_client_secret + volumeMounts: + - name: dynamic-storage + mountPath: /home/storage + - name: file-connector-storage + mountPath: /home/file_connector_storage + volumes: + - name: dynamic-storage + persistentVolumeClaim: + claimName: dynamic-pvc + - name: file-connector-storage + persistentVolumeClaim: + claimName: file-connector-pvc diff --git a/deployment/kubernetes/background-deployment.yaml b/deployment/kubernetes/background-deployment.yaml new file mode 100644 index 000000000000..4cbc720c06d3 --- /dev/null +++ b/deployment/kubernetes/background-deployment.yaml @@ -0,0 +1,42 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: background-deployment +spec: + replicas: 1 + selector: + matchLabels: + app: background + template: + metadata: + labels: + app: background + spec: + containers: + - name: background + image: danswer/danswer-background:latest + imagePullPolicy: IfNotPresent + env: + - name: POSTGRES_HOST + value: relational-db-service + - name: QDRANT_HOST + value: vector-db-service + - name: TYPESENSE_HOST + value: search-engine-service + - name: TYPESENSE_API_KEY + valueFrom: + secretKeyRef: + name: danswer-secrets + key: typesense_api_key + volumeMounts: + - name: dynamic-storage + mountPath: /home/storage + - name: file-connector-storage + mountPath: /home/file_connector_storage + volumes: + - name: dynamic-storage + persistentVolumeClaim: + claimName: dynamic-pvc + - name: file-connector-storage + persistentVolumeClaim: + claimName: file-connector-pvc diff --git a/deployment/kubernetes/nginx-configmap.yaml b/deployment/kubernetes/nginx-configmap.yaml new file mode 100644 index 000000000000..0468dc5b07d9 --- /dev/null +++ b/deployment/kubernetes/nginx-configmap.yaml @@ -0,0 +1,42 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: nginx-configmap +data: + nginx.conf: | + upstream api_server { + server api-server-service:80 fail_timeout=0; + } + + upstream web_server { + server web-server-service:80 fail_timeout=0; + } + + server { + listen 80; + server_name $$DOMAIN; + + location ~ ^/api(.*)$ { + rewrite ^/api(/.*)$ $1 break; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header X-Forwarded-Host $host; + proxy_set_header Host $host; + proxy_http_version 1.1; + proxy_buffering off; + proxy_redirect off; + proxy_pass http://api_server; + } + + location / { + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header X-Forwarded-Host $host; + proxy_set_header Host $host; + proxy_http_version 1.1; + proxy_redirect off; + proxy_pass http://web_server; + } + } diff --git a/deployment/kubernetes/nginx-service-deployment.yaml b/deployment/kubernetes/nginx-service-deployment.yaml new file mode 100644 index 000000000000..81b660dff192 --- /dev/null +++ b/deployment/kubernetes/nginx-service-deployment.yaml @@ -0,0 +1,52 @@ +apiVersion: v1 +kind: Service +metadata: + name: nginx-service +spec: + selector: + app: nginx + ports: + - name: http + protocol: TCP + port: 80 + targetPort: 80 + - name: danswer + protocol: TCP + port: 3000 + targetPort: 80 + type: LoadBalancer +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: nginx-deployment +spec: + replicas: 1 + selector: + matchLabels: + app: nginx + template: + metadata: + labels: + app: nginx + spec: + containers: + - name: nginx + image: nginx:1.23.4-alpine + ports: + - containerPort: 80 + env: + - name: DOMAIN + value: localhost + volumeMounts: + - name: nginx-conf + mountPath: /etc/nginx/conf.d + command: + - /bin/sh + - -c + - | + while :; do sleep 6h & wait $$!; nginx -s reload; done & nginx -g "daemon off;" + volumes: + - name: nginx-conf + configMap: + name: nginx-configmap diff --git a/deployment/kubernetes/persistent-volumes.yaml b/deployment/kubernetes/persistent-volumes.yaml new file mode 100644 index 000000000000..df83213792b8 --- /dev/null +++ b/deployment/kubernetes/persistent-volumes.yaml @@ -0,0 +1,54 @@ +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: dynamic-pvc +spec: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 1Gi +--- +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: file-connector-pvc +spec: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 1Gi +--- +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: db-volume-claim +spec: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 1Gi +--- +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: qdrant-pvc +spec: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 1Gi +--- +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: typesense-pvc +spec: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 1Gi \ No newline at end of file diff --git a/deployment/kubernetes/postgres-service-deployment.yaml b/deployment/kubernetes/postgres-service-deployment.yaml new file mode 100644 index 000000000000..5b78aebe664c --- /dev/null +++ b/deployment/kubernetes/postgres-service-deployment.yaml @@ -0,0 +1,50 @@ +apiVersion: v1 +kind: Service +metadata: + name: relational-db-service +spec: + selector: + app: relational-db + ports: + - protocol: TCP + port: 5432 + targetPort: 5432 + type: ClusterIP +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: relational-db-deployment +spec: + replicas: 1 + selector: + matchLabels: + app: relational-db + template: + metadata: + labels: + app: relational-db + spec: + containers: + - name: relational-db + image: postgres:15.2-alpine + env: + - name: POSTGRES_USER + valueFrom: + secretKeyRef: + name: danswer-secrets + key: postgres_user + - name: POSTGRES_PASSWORD + valueFrom: + secretKeyRef: + name: danswer-secrets + key: postgres_password + ports: + - containerPort: 5432 + volumeMounts: + - mountPath: /var/lib/postgresql/data + name: db-volume + volumes: + - name: db-volume + persistentVolumeClaim: + claimName: db-volume-claim diff --git a/deployment/kubernetes/qdrant-service-deployment.yaml b/deployment/kubernetes/qdrant-service-deployment.yaml new file mode 100644 index 000000000000..4ee48e5164fe --- /dev/null +++ b/deployment/kubernetes/qdrant-service-deployment.yaml @@ -0,0 +1,40 @@ +apiVersion: v1 +kind: Service +metadata: + name: vector-db-service +spec: + selector: + app: qdrant + ports: + - name: qdrant-port + protocol: TCP + port: 6333 + targetPort: 6333 + type: LoadBalancer +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: qdrant-deployment +spec: + replicas: 1 + selector: + matchLabels: + app: qdrant + template: + metadata: + labels: + app: qdrant + spec: + containers: + - name: qdrant + image: qdrant/qdrant:v1.1.3 + ports: + - containerPort: 6333 + volumeMounts: + - name: qdrant-storage + mountPath: /qdrant/storage + volumes: + - name: qdrant-storage + persistentVolumeClaim: + claimName: qdrant-pvc diff --git a/deployment/kubernetes/secrets.yaml b/deployment/kubernetes/secrets.yaml new file mode 100644 index 000000000000..33e459b081fe --- /dev/null +++ b/deployment/kubernetes/secrets.yaml @@ -0,0 +1,12 @@ +# The values in this file should be changed +apiVersion: v1 +kind: Secret +metadata: + name: danswer-secrets +type: Opaque +data: + postgres_user: cG9zdGdyZXM= # "postgres" base64 encoded + postgres_password: cGFzc3dvcmQ= # "password" base64 encoded + typesense_api_key: bG9jYWxfZGV2X3R5cGVzZW5zZQ== # "local_dev_typesense" base64 encoded + google_oauth_client_id: REPLACE-THIS # You will need to provide this, use echo -n "your-client-id" | base64 + google_oauth_client_secret: REPLACE-THIS # You will need to provide this, use echo -n "your-client-id" | base64 diff --git a/deployment/kubernetes/typesense-service-deployment.yaml b/deployment/kubernetes/typesense-service-deployment.yaml new file mode 100644 index 000000000000..b2ea6231ff96 --- /dev/null +++ b/deployment/kubernetes/typesense-service-deployment.yaml @@ -0,0 +1,48 @@ +apiVersion: v1 +kind: Service +metadata: + name: search-engine-service +spec: + selector: + app: typesense + ports: + - name: typesense-port + protocol: TCP + port: 8108 + targetPort: 8108 + type: LoadBalancer +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: typesense-deployment +spec: + replicas: 1 + selector: + matchLabels: + app: typesense + template: + metadata: + labels: + app: typesense + spec: + containers: + - name: typesense + image: typesense/typesense:0.24.1 + ports: + - containerPort: 8108 + env: + - name: TYPESENSE_API_KEY + valueFrom: + secretKeyRef: + name: danswer-secrets + key: typesense_api_key + - name: TYPESENSE_DATA_DIR + value: /typesense/storage + volumeMounts: + - name: typesense-storage + mountPath: /typesense/storage + volumes: + - name: typesense-storage + persistentVolumeClaim: + claimName: typesense-pvc diff --git a/deployment/kubernetes/web_server-service-deployment.yaml b/deployment/kubernetes/web_server-service-deployment.yaml new file mode 100644 index 000000000000..f1329c638f27 --- /dev/null +++ b/deployment/kubernetes/web_server-service-deployment.yaml @@ -0,0 +1,36 @@ +apiVersion: v1 +kind: Service +metadata: + name: web-server-service +spec: + selector: + app: web-server + ports: + - protocol: TCP + port: 80 + targetPort: 3000 + type: ClusterIP +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: web-server-deployment +spec: + replicas: 1 + selector: + matchLabels: + app: web-server + template: + metadata: + labels: + app: web-server + spec: + containers: + - name: web-server + image: danswer/danswer-web-server:latest + imagePullPolicy: IfNotPresent + ports: + - containerPort: 3000 + env: + - name: INTERNAL_URL + value: "http://api-server-service:80"