Skip to content

Commit

Permalink
Prod Updates (#32)
Browse files Browse the repository at this point in the history
* temporal annotation models and endpoints

* migrations for temporal endpoints

* updating migrations

* basics of rendering temporal annotations

* linting

* temporal creation/editing/deletion

* supporting multiple users with sequence and pulse annotations

* client cleanup of minor issues

* Draft system for prod deployment. (#31)

* adding prod-deployment infrastructure

* adding environment variables

* reconfigure items

* updating prod config

* updating prod config

* allowed hosts

* adding fake email url

* new configuration

* new configuration

* allowed hosts'

* prod config

* add network to celery

* prod

* base configuration

* adding traefik

* update traefik

* port fix

* traefik config

* traefik config

* remove traefik

* Temporal Annotations (#30)

* temporal annotation models and endpoints

* migrations for temporal endpoints

* updating migrations

* basics of rendering temporal annotations

* linting

* temporal creation/editing/deletion

* supporting multiple users with sequence and pulse annotations

* client cleanup of minor issues

* prod update

* updating hostname

* updating hostname

* traefik routing

* update nginx

* updating traefik

* reverting to nginx proxy

* issuer cert

* trusted origins

* add https mixin to configuration

* update configuration

* remove https

* refactoring prod .env files

* refactoring environment files

* update docker

* linting fix

* add server hostname to environment

* deployment docs update

* deployment docs update
  • Loading branch information
BryonLewis authored Feb 21, 2024
1 parent ebcebcf commit cd3ad21
Show file tree
Hide file tree
Showing 13 changed files with 426 additions and 14 deletions.
82 changes: 82 additions & 0 deletions DEPLOYMENT.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
# bats-ai

## Deployment with Docker (recommended quickstart)

This was a bit rushed so the deployment utilizes a single
docker file `docker-compose.prod.yml` in the root of the directory

I wanted some simple instructions below to configure the deployment

Be sure to use the proper hostname (batdetectai.kitware.com) in
all locations that require it.

## Docker Compose Differences

I created a `client` service which has it's own Dockerfile and
builds the vue client app.
The `client` service also uses a reverse proxy to route
`/api`, `/admin` fields to the django server.
The client will need to be built with a different Client ID
for accessing the server.

### Initial Setup for Deployment

1. Run `docker compose run --rm django ./manage.py migrate`
2. Run `docker compose run --rm django ./manage.py createsuperuser`
and follow the prompts to create your own user
3. Run `docker compose run --rm django ./manage.py makeclient \
--username [email protected] \
--uri https://batdetectai.kitware.com/`
4. Run `docker compose run --rm django ./manage.py loaddata species` to load species
data into the database
5. Run `docker compose run --rm django ./manage.py collectstatic`
to collect the static files
6. Run `docker compose -f docker-compose.prod.yml up` to start the server
add `-d` for a silent version to run in the background
7. Copy over the ./dev/.env.prod.docker-compose.template
to `./dev/.env.prod.docker-compose.template` and change the default passwords
8. Change the ID in the `./client/env.production` to a custom ID - this will
probably require a `docker compose build` to build the app afterwards
9. After creating the basic application log into the django admin `batdetectai.kitware.com/admin`
and change the ApplicationId to the ID in the `./client.env.production`
10. Test logging in/out and uploading data to the server.

### system.d service

Service that will automatically start and launch the server
Create this at `/etc/systemd/system` using sudo

```systemd
[Unit]
Description=batai-server
Requires=docker.service
After=docker.service
[Service]
ExecStartPre=/bin/sleep 10
Environment=PATH=/usr/bin:/sbin:/usr/sbin:/usr/local/sbin:/usr/local/bin
Restart=always
User=bryon
Group=docker
TimeoutStartSec=300
RestartSec=20
WorkingDirectory=/home/bryon/batai
# Shutdown container (if running) when unit is started
ExecStartPre=docker compose down
# Start container when unit is started
ExecStart=docker compose -f docker-compose.prod.yml up
# Stop container when unit is stopped
ExecStop=docker compose down
[Install]
WantedBy=multi-user.target
```

After run `sudo systemctl enable batai.service`
Then to start you can use `sudo systemctl start batai.service`
Stopping: `sudo systemctl stop batai.service`

### User Management

There is no email server connected up so users need to be
individually approved and their email verified by an admin
32 changes: 28 additions & 4 deletions bats_ai/settings.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from __future__ import annotations

import os
from pathlib import Path

from composed_configuration import (
Expand All @@ -10,12 +11,9 @@
ProductionBaseConfiguration,
TestingBaseConfiguration,
)
from composed_configuration._configuration import _BaseConfiguration
from configurations import values

CORS_ALLOWED_ORIGINS = [
'http://localhost:3000',
]


class BatsAiMixin(ConfigMixin):
WSGI_APPLICATION = 'bats_ai.wsgi.application'
Expand Down Expand Up @@ -80,6 +78,32 @@ class TestingConfiguration(BatsAiMixin, TestingBaseConfiguration):
pass


class KitwareConfiguration(BatsAiMixin, _BaseConfiguration):
SECRET_KEY = values.SecretValue()
baseHost = 'batdetectai.kitware.com'
if 'SERVERHOSTNAME' in os.environ:
baseHost = os.environ['SERVERHOSTNAME']
EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'
DEFAULT_FILE_STORAGE = 'minio_storage.storage.MinioMediaStorage'
MINIO_STORAGE_ENDPOINT = values.Value(
'minio:9000',
)
MINIO_STORAGE_USE_HTTPS = values.BooleanValue(False)
MINIO_STORAGE_ACCESS_KEY = values.SecretValue()
MINIO_STORAGE_SECRET_KEY = values.SecretValue()
MINIO_STORAGE_MEDIA_BUCKET_NAME = values.Value(
environ_name='STORAGE_BUCKET_NAME',
environ_required=True,
)
MINIO_STORAGE_AUTO_CREATE_MEDIA_BUCKET = True
MINIO_STORAGE_AUTO_CREATE_MEDIA_POLICY = 'READ_WRITE'
MINIO_STORAGE_MEDIA_USE_PRESIGNED = True
MINIO_STORAGE_MEDIA_URL = 'http://127.0.0.1:9000/django-storage'
ALLOWED_HOSTS = [baseHost]
CSRF_TRUSTED_ORIGINS = [f'https://{baseHost}', f'https://{baseHost}']
CORS_ORIGIN_WHITELIST = [f'https://{baseHost}', f'https://{baseHost}']


class ProductionConfiguration(BatsAiMixin, ProductionBaseConfiguration):
pass

Expand Down
7 changes: 3 additions & 4 deletions client/.env.production
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
VUE_APP_API_ROOT=https://CHANGEME/api/v1
VUE_APP_OAUTH_API_ROOT=https://CHANGEME/oauth/
VUE_APP_OAUTH_CLIENT_ID=CHANGEME
VUE_APP_SENTRY_DSN=CHANGEME
VUE_APP_API_ROOT=https://batdetectai.kitware.com/api/v1
VUE_APP_OAUTH_API_ROOT=https://batdetectai.kitware.com/oauth/
VUE_APP_OAUTH_CLIENT_ID=HSJWFZ2cIpWQOvNyCXyStV9hiOd7DfWeBOCzo4pP
3 changes: 3 additions & 0 deletions client/.env.production.test
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
VUE_APP_API_ROOT=http://localhost/api/v1
VUE_APP_OAUTH_API_ROOT=http://localhost/oauth/
VUE_APP_OAUTH_CLIENT_ID=HSJWFZ2cIpWQOvNyCXyStV9hiOd7DfWeBOCzo4pP
4 changes: 2 additions & 2 deletions client/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,8 @@ git grep CHANGEME

## Recommended IDE Setup

- [VSCode](https://code.visualstudio.com/)
- [Volar](https://marketplace.visualstudio.com/items?itemName=johnsoncodehk.volar)
* [VSCode](https://code.visualstudio.com/)
* [Volar](https://marketplace.visualstudio.com/items?itemName=johnsoncodehk.volar)

## Type Support For `.vue` Imports in TS

Expand Down
2 changes: 2 additions & 0 deletions dev/.env.docker-compose
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
DJANGO_CONFIGURATION=DevelopmentConfiguration
DJANGO_DATABASE_NAME=django
DJANGO_DATABASE_PASSWORD=postgres
DJANGO_DATABASE_URL=postgres://postgres:postgres@postgres:5432/django
DJANGO_CELERY_BROKER_URL=amqp://rabbitmq:5672/
DJANGO_MINIO_STORAGE_ENDPOINT=minio:9000
Expand Down
13 changes: 13 additions & 0 deletions dev/.env.prod.docker-compose.template
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
DJANGO_CONFIGURATION=KitwareConfiguration
DJANGO_DATABASE_NAME=django
DJANGO_DATABASE_PASSWORD=postgres
DJANGO_DATABASE_URL=postgres://postgres:postgres@postgres:5432/django
DJANGO_CELERY_BROKER_URL=amqp://rabbitmq:5672/
DJANGO_MINIO_STORAGE_ENDPOINT=minio:9000
DJANGO_MINIO_STORAGE_ACCESS_KEY=minioAccessKey
DJANGO_MINIO_STORAGE_SECRET_KEY=minioSecretKey
DJANGO_STORAGE_BUCKET_NAME=django-storage
DJANGO_MINIO_STORAGE_ENDPOINT=minio:9000
SERVERHOSTNAME=batdetectai.kitware.com
DJANGO_SECRET_KEY=changeme
[email protected]
36 changes: 36 additions & 0 deletions dev/client.Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# Use official Node.js image as the base image for building Vue.js app
FROM node:16 as build-stage

# Set working directory
WORKDIR /app

# Copy package.json and package-lock.json
COPY client/package*.json ./

# Install dependencies
RUN npm install

# Copy the rest of the application
COPY client .

# Build the Vue.js application
RUN npm run build

# Use NGINX as the final base image
FROM nginx:alpine

# Remove default NGINX website
RUN rm -rf /usr/share/nginx/html/*

# Copy built Vue.js app to NGINX HTML directory
COPY --from=build-stage /app/dist /usr/share/nginx/html

RUN ls
# Copy custom NGINX configuration
COPY nginx/nginx.conf /etc/nginx/nginx.conf

# Expose port 80
EXPOSE 80

# Start NGINX
CMD ["nginx", "-g", "daemon off;"]
172 changes: 172 additions & 0 deletions docker-compose.prod.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
version: '3.8'

services:
# COMMENTED OUT UNTIL READY TO TEST
traefik:
restart: always
image: traefik:v2.4
container_name: traefik
env_file: ./dev/.env.prod.docker-compose
networks:
- django-nginx
command: >
--providers.docker=true
--providers.docker.exposedByDefault=false
--log.level=${LOG_LEVEL:-WARN}
--providers.docker.exposedByDefault=false
--providers.file.filename=/var/traefik/dynamic.yml
--entrypoints.web.address=:80
--entrypoints.websecure.address=:443
--entrypoints.websecure.http.tls.certresolver=myresolver
--certificatesresolvers.myresolver.acme.email=${ACME_EMAIL:[email protected]}
--certificatesresolvers.myresolver.acme.storage=/letsencrypt/acme.json
--certificatesresolvers.myresolver.acme.httpchallenge=true
--certificatesresolvers.myresolver.acme.httpchallenge.entrypoint=web
--certificatesresolvers.myresolver.acme.caserver=${ACME_CA_SERVER:-https://acme-v02.api.letsencrypt.org/directory}
labels:
# Traefik HTTPS Redirect
- "traefik.enable=true"
- "traefik.http.routers.http-catchall.entrypoints=web"
- "traefik.http.routers.http-catchall.rule=Host(`${SERVERHOSTNAME:-batdetectai.kitware.com}`)"
- "traefik.http.routers.http-catchall.middlewares=redirect-to-https-mddl@docker"
- "traefik.http.middlewares.redirect-to-https-mddl.redirectscheme.scheme=https"
volumes:
- "${SOCK_PATH:-/var/run/docker.sock}:/var/run/docker.sock"
- "./traefik/letsencrypt:/letsencrypt"
- "./traefik/dynamic.yml:/var/traefik/dynamic.yml:ro"
ports:
- "80:80"
- "443:443"

django:
build:
context: .
dockerfile: ./dev/django.Dockerfile
command: 'gunicorn bats_ai.wsgi:application --bind 0.0.0.0:8000'
# ./manage.py runserver 0.0.0.0:8000 --noreload
# entrypoint: ["/bin/bash"]
# command: ""
# Log printing via Rich is enhanced by a TTY
tty: true
env_file: ./dev/.env.prod.docker-compose
networks:
- django-nginx
volumes:
- .:/opt/django-project
environment:
- SERVERHOSTNAME=${SERVERHOSTNAME:-batdetectai.kitware.com}
ports:
- 8000:8000
depends_on:
- postgres
- rabbitmq
- minio
celery:
build:
context: .
dockerfile: ./dev/django.Dockerfile
command: [
"celery",
"--app", "bats_ai.celery",
"worker",
"--loglevel", "INFO",
"--without-heartbeat"
]
# Docker Compose does not set the TTY width, which causes Celery errors
tty: false
env_file: ./dev/.env.prod.docker-compose
networks:
- django-nginx
volumes:
- .:/opt/django-project
depends_on:
- postgres
- rabbitmq
- minio
client:
build:
context: .
dockerfile: ./dev/client.Dockerfile
env_file: ./dev/.env.prod.docker-compose
networks:
- django-nginx
depends_on:
- django
labels:
- "traefik.http.routers.client-rtr.entrypoints=websecure"
- "traefik.http.routers.client-rtr.rule=Host(`${SERVERHOSTNAME:-batdetectai.kitware.com}`)"
- "traefik.enable=true"
- "traefik.http.services.client-svc.loadbalancer.server.port=80"
postgres:
image: postgis/postgis:latest
env_file: ./dev/.env.prod.docker-compose
environment:
- POSTGRES_DB=${DJANGO_DATABASE_NAME:-django}
- POSTGRES_PASSWORD=${DJANGO_MINIO_STORAGE_SECRET_KEY:-postgres}
networks:
- django-nginx
ports:
- ${DOCKER_POSTGRES_PORT-5432}:5432
volumes:
- postgres:/var/lib/postgresql/data

rabbitmq:
env_file: ./dev/.env.prod.docker-compose
image: rabbitmq:management
networks:
- django-nginx
ports:
- ${DOCKER_RABBITMQ_PORT-5672}:5672
- ${DOCKER_RABBITMQ_CONSOLE_PORT-15672}:15672
volumes:
- rabbitmq:/var/lib/rabbitmq/mnesia

minio:
image: minio/minio:latest
# When run with a TTY, minio prints credentials on startup
tty: true
command: ["server", "/data", "--console-address", ":${DOCKER_MINIO_CONSOLE_PORT-9001}"]
env_file: ./dev/.env.prod.docker-compose
environment:
- MINIO_ROOT_USER=${DJANGO_MINIO_STORAGE_ACCESS_KEY:-minioAccessKey}
- MINIO_ROOT_PASSWORD=${DJANGO_DATABASE_PASSWORD:-minioSecretKey}
networks:
- django-nginx
ports:
- ${DOCKER_MINIO_PORT-9000}:9000
- ${DOCKER_MINIO_CONSOLE_PORT-9001}:9001
volumes:
- minio:/data

flower:
env_file: ./dev/.env.prod.docker-compose
build:
context: .
dockerfile: ./dev/django.Dockerfile
command: [
"celery",
"--app", "bats_ai.celery",
"flower"
]
tty: false
volumes:
- .:/opt/django-project
networks:
- django-nginx
ports:
- ${DOCKER_FLOWER_PORT-5555}:5555
depends_on:
- postgres
- rabbitmq
- minio
- celery

volumes:
postgres:
sourcedb:
minio:
rabbitmq:

networks:
django-nginx:
driver: bridge
Loading

0 comments on commit cd3ad21

Please sign in to comment.