diff --git a/server/Dockerfile b/server/Dockerfile new file mode 100644 index 0000000..7157d11 --- /dev/null +++ b/server/Dockerfile @@ -0,0 +1,28 @@ +FROM python:3.8.12-bullseye as builder +ENV VIRTUAL_ENV=/opt/venv +RUN pip install --no-cache-dir poetry virtualenv +RUN virtualenv ${VIRTUAL_ENV} +ENV PATH="${VIRTUAL_ENV}/bin:$PATH" + +COPY pyproject.toml poetry.lock ./ +RUN poetry install --no-dev --no-root + +COPY ./src /src +RUN poetry build -f wheel + +RUN ls dist -a +RUN pip install ./dist/* + + +FROM python:3.8.12-slim-bullseye +ENV VIRTUAL_ENV=/opt/venv + +COPY --from=builder ${VIRTUAL_ENV} ${VIRTUAL_ENV} +ENV PATH="${VIRTUAL_ENV}/bin:$PATH" + +COPY ./app /app + +# if running in kubernetes, or other orchestration env, uvicorn is good enough +# CMD ["uvicorn", "app.main:app"] + +CMD ["gunicorn", "-c", "app/gunicorn_conf.py", "-k", "uvicorn.workers.UvicornWorker", "app.main:app" ] \ No newline at end of file diff --git a/server/README.md b/server/README.md index 048e48a..a0b4c97 100644 --- a/server/README.md +++ b/server/README.md @@ -4,4 +4,6 @@ This app is the product of the first SpeckleHackathon To run the app, use the provided `Dockerfile` -To debug the app, create a `.env` file based on the `.env_example` and see the `.vscode/launch.json` for a sample config. \ No newline at end of file +To debug the app, create a `.env` file based on the `.env_example` and see the `.vscode/launch.json` for a sample config. + +Use the example hooks provided, and the `http://localhost:8000/docs` page to test things. \ No newline at end of file diff --git a/server/app/gunicorn_conf.py b/server/app/gunicorn_conf.py new file mode 100644 index 0000000..26eee25 --- /dev/null +++ b/server/app/gunicorn_conf.py @@ -0,0 +1,67 @@ +import json +import multiprocessing +import os + +workers_per_core_str = os.getenv("WORKERS_PER_CORE", "1") +max_workers_str = os.getenv("MAX_WORKERS") +use_max_workers = None +if max_workers_str: + use_max_workers = int(max_workers_str) +web_concurrency_str = os.getenv("WEB_CONCURRENCY", None) + +host = os.getenv("HOST", "0.0.0.0") +port = os.getenv("PORT", "80") +bind_env = os.getenv("BIND", None) +use_loglevel = os.getenv("LOG_LEVEL", "info") +if bind_env: + use_bind = bind_env +else: + use_bind = f"{host}:{port}" + +cores = multiprocessing.cpu_count() +workers_per_core = float(workers_per_core_str) +default_web_concurrency = workers_per_core * cores +if web_concurrency_str: + web_concurrency = int(web_concurrency_str) + assert web_concurrency > 0 +else: + web_concurrency = max(int(default_web_concurrency), 2) + if use_max_workers: + web_concurrency = min(web_concurrency, use_max_workers) +accesslog_var = os.getenv("ACCESS_LOG", "-") +use_accesslog = accesslog_var or None +errorlog_var = os.getenv("ERROR_LOG", "-") +use_errorlog = errorlog_var or None +graceful_timeout_str = os.getenv("GRACEFUL_TIMEOUT", "120") +timeout_str = os.getenv("TIMEOUT", "120") +keepalive_str = os.getenv("KEEP_ALIVE", "5") + +# Gunicorn config variables +loglevel = use_loglevel +workers = web_concurrency +bind = use_bind +errorlog = use_errorlog +worker_tmp_dir = "/dev/shm" +accesslog = use_accesslog +graceful_timeout = int(graceful_timeout_str) +timeout = int(timeout_str) +keepalive = int(keepalive_str) + + +# For debugging and testing +log_data = { + "loglevel": loglevel, + "workers": workers, + "bind": bind, + "graceful_timeout": graceful_timeout, + "timeout": timeout, + "keepalive": keepalive, + "errorlog": errorlog, + "accesslog": accesslog, + # Additional, non-gunicorn variables + "workers_per_core": workers_per_core, + "use_max_workers": use_max_workers, + "host": host, + "port": port, +} +print(json.dumps(log_data)) \ No newline at end of file diff --git a/server/app/main.py b/server/app/main.py index 1df58dc..3893f80 100644 --- a/server/app/main.py +++ b/server/app/main.py @@ -12,6 +12,7 @@ from app.models import Webhook app = FastAPI() +print("booting") @app.get("/health") async def health():