hackoregon / backend-examplar-2018

an example dockerized geo-aware django backend
MIT License
4 stars 5 forks source link

Document Steps that have been taken to configure the "Production" environment #47

Open bhgrant8 opened 6 years ago

bhgrant8 commented 6 years ago

So as a starting point I am copying over and updating what was configured for the current state of the "production" environment.

Original: https://github.com/hackoregon/transportation-system-backend#run-in-staging-environment

Run in a Production Environment

While developing the API, using the built in dev server is useful as it allows for live reloading, and debug messages. When running in a production environment, this is a security risk, and not efficient.

As such a production environment has been configured to be run when using the build.sh -p and start.sh -p commands.

A quick summary of python packages and why they are in there:

Gunicorn - A "green" HTTP server - http://gunicorn.org/ Gevent - Asynchronous workers - http://www.gevent.org/ Pyscopgreen - A "green" version of the psycop database connector - https://pypi.org/project/psycogreen/ django_db_geventpool - DB pool using gevent for PostgreSQL DB. - https://github.com/jneight/django-db-geventpool/tree/master/django_db_geventpool WhiteNoise - allows for hosting of static files by gunicorn in a prod environment vs. integrating a webserver - http://whitenoise.evans.io/en/stable/

This provides a fairly stable, often used stack for django deployment, basically staying within python for the bulk of our tools, and cutting out the need for a separate server for the swagger/browsable front end

To start it:

  1. Configure the PRODUCTION_ variables in the .env file (you will want to work with devops team to get these. They will need to match the production or staging server). The idea here is that you will now be connecting to a live database environment an AWS or otherwise externally hosted and not running the local database container. (see below for additional details)

  2. Run the build.sh script to build the project for the staging environment: $ ./bin/build.sh -p

  3. Start the project using the staging flag: $ ./bin/start.sh -p

Open your browser and you should be able to access the Django Restframework browserable front end at: http://localhost:8000/api and Swagger at http://localhost:8000/schema

Try going to an nonexistent page and you should see a generic 404 Not found page instead of the Django debug screen.

What was configured: So this is what has been configured between various files:

  1. Add gunicorn, gevent, and whitenoise, django-db-geventpool, to requirements/production.txt
  2. Set the debug variable to false in the the production-docker-compose file
  3. make any other changes necessary to config vars, ie: database settings
  4. create a prod entrypoint file that runs the gunicorn start command instead of the ./manage.py runserver. Here is an example: gunicorn crash_data_api.wsgi -c gunicorn_config.py
  5. create the gunicorn_config.py file to hold gunicorn config, including using gevent worker_class.

Currently we are patching psycopg2 and django with gevent/psycogreen in the post_fork worker. Also using 4 workers (which maybe too many?):

try:
    # fail 'successfully' if either of these modules aren't installed
    from gevent import monkey
    from psycogreen.gevent import patch_psycopg

    # setting this inside the 'try' ensures that we only
    # activate the gevent worker pool if we have gevent installed
    worker_class = 'gevent'
    workers = 4
    # this ensures forked processes are patched with gevent/gevent-psycopg2
    def do_post_fork(server, worker):
        monkey.patch_all()
        patch_psycopg()

        # you should see this text in your gunicorn logs if it was successful
        worker.log.info("Made Psycopg2 Green")

    post_fork = do_post_fork
except ImportError:
    pass
  1. ChangeD the settings.py file to use django_db_geventpool when in production mode. You will add this after the current database settings.:
if os.environ.get('DEBUG') == "False":

    DATABASES = {
        'default': {
            'ENGINE': 'django_db_geventpool.backends.postgis',
            'PASSWORD': os.environ.get('POSTGRES_PASSWORD'),
            'NAME': os.environ.get('POSTGRES_NAME'),
            'USER': os.environ.get('POSTGRES_USER'),
            'HOST': os.environ.get('POSTGRES_HOST'),
            'PORT': os.environ.get('POSTGRES_PORT'),
            'CONN_MAX_AGE': 0,
            'OPTIONS': {
                'MAX_CONNS': 20
            }
        }
    }
  1. create a staging/production docker_compose file, using correct env vars, entrypoint command, and removing the database container:
version: '3.4'
services:
  api_production:
    build:
      context: .
      dockerfile: DOCKERFILE.api.production
    image: api_production
    command: ./bin/production-docker-entrypoint.sh
    volumes:
      - .:/code
    ports:
      - "8000:8000"
    environment:
      - PROJECT_NAME
      - DEBUG=False
      - POSTGRES_USER=${PRODUCTION_POSTGRES_USER}
      - POSTGRES_NAME=${PRODUCTION_POSTGRES_NAME}
      - POSTGRES_HOST=${PRODUCTION_POSTGRES_HOST}
      - POSTGRES_PORT=${PRODUCTION_POSTGRES_PORT}
      - POSTGRES_PASSWORD=${PRODUCTION_POSTGRES_PASSWORD}
      - DJANGO_SECRET_KEY=${PRODUCTION_DJANGO_SECRET_KEY}
  1. Make changes to settings.py to check the debug variable and use : Change DEBUG line:
    DEBUG = os.environ.get('DEBUG') == "True" - handles os variables being treated as strings

    ADD to MIDDLEWARE right after SECURITY:

    'whitenoise.middleware.WhiteNoiseMiddleware',

    ADD these just before the STATIC_URL so staticfiles are handled correctly and are compressed:

STATICFILES_STORAGE = 'whitenoise.storage.CompressedManifestStaticFilesStorage'

STATIC_ROOT = os.path.join(BASE_DIR, 'staticfiles')
bhgrant8 commented 6 years ago

one warning that has come up with this current setup is https://github.com/hackoregon/civic-devops/issues/13

unknown impact, whether is just noise or needs to be addressed