Deployment

Hier ein paar Tipps, wie ein Projekt heutzutage deployed wird. Django selbst bietet eine Checkliste, was alles gemacht werden muss, um sauber uns sicher zu Deployen

https://docs.djangoproject.com/en/stable/howto/deployment/checklist/

Git

Das Projekt sollte via git versioniert und bei github gehostet werden.

GitHooks

kein Code sollte ins share repository wie github gehen, der schlecht formatiert ist oder Test-Fehler enthält. Um diesem Problem entgegenzuwirken, lassen sich sogenannte Hooks einrichten. Der Wichtigste Hook ist der pre-commit-hook, der VOR dem Commit den Code testet und dann erst den Commit zulässt. Konfiguriert wird das ganze in einer pre-commit-config.yaml-Datei.

repos:
  - repo: https://github.com/pre-commit/pre-commit-hooks
    rev: v3.ref
    hooks:
      - id: check-docstring-first
      - id: check-merge-conflict
      - id: trailing-whitespace
      - id: end-of-file-fixer
      - id: check-yaml
      - id: check-ast
      - id: check-toml

  - repo: https://gitlab.com/pycqa/flake8
    rev: 3.9.2
    hooks:
      - id: flake8
        args: ['--config=setup.cfg', '--ignore=Fstable,E501,F405,F403']
        exclude: ^events/tests/
  - repo: local
    hooks:
      - id: django-test
        name: django-test
        entry: python manage.py test
        always_run: true
        pass_filenames: false
        language: system
  - repo: https://github.com/pycqa/isort
    rev: 5.9.3
    hooks:
      - id: isort
        name: isort (python)

Dieser Pre-Commit-Hook zum Beisiel prüft den Code auf flake8-Verstöße, führt mit manage.py test die Tests durch, sortiert die Imports mit isort und prüft merge-Konflikte, Yaml und Toml - Dateien.

CI/CD - Pipeline

Heute kommt kein Projekt mehr ohne Continous Integration bzw. Continous Deployment aus. Eine Einführung findet sich hier: https://www.freecodecamp.org/news/how-to-setup-a-ci-cd-pipeline-with-github-actions-and-aws/

Docker

Wenn im Team gearbeitet wird, gibt es unweigerlich Unterschiede in der Konfiguration zwischen den lokalen Rechnern der einzelnen Entwickler. Ganz zu schweigen von dem Unterschied lokale Konfiguration mit der produktiven Konfiguration. Um diesem Problem entgegenzuwirken, wird heute fast ausschließlich mit Containern gearbeitet. So kann sichergestellt werden, dass jede lokale Instanz des Projekts inkl. der Peripherie wie Datenbank, Message-Broker, Celery etc, identisch mit der Produktiv-Instanz ist.

Eine kleine Einführung gibt es hier: https://docs.docker.com/samples/django/ Ansonsten finden sich unzählige Tutorials im Internet.

Docker Container sind einfach skalierbar und zum Beispiel mit Kubernetes orchestrierbar.

eine einfache docker-compose - Datei für die lokale Entwicklungsumgebung könnte so aussehen:

version: '3.8'

services:
  app:
    build: ./app
    command: >
      sh -c "python manage.py runserver 0.0.0.0:8000"
    volumes:
      - ./app/:/usr/src/app/
    ports:
      - 8000:8000
    env_file:
      - ./.env
    depends_on:
      - db
      - redis

  db:
    image: postgres:13.0-alpine
    volumes:
      - postgres_data:/var/lib/postgresql/data/
    environment:
      - POSTGRES_USER=basepro
      - POSTGRES_PASSWORD=basepro
      - POSTGRES_DB=basepro_dev

  redis:
    image: redis:alpine

volumes:
  postgres_data:
    driver: local

Auf Sicherheitslücken prüfen

Auf dem Livesystem sollte beim ersten Deploy festgestellt werden, ob es Sicherheitslücken gibt. Diese gleich fixen.

> python manage.py check --deploy


WARNINGS:
?: (security.W004) You have not set a value for the SECURE_HSTS_SECONDS setting.
If your entire site is served only over SSL, you may want to consider setting a
value and enabling HTTP Strict Transport Security. Be sure to read the
documentation first; enabling HSTS carelessly can cause serious, irreversible
problems.

Fehler still stellen

Manche Fehler werden an anderere Stelle besser behoben. Zum Beispiel werden die HSTS-Header ( HTTP Strict-Transport-Security response header ) oft von NGINX gesetzt. Dann kann man die Fehlermeldungen in den settings.py auch muten:

SILENCED_SYSTEM_CHECKS = ["security.W004"]

Webserver

Bisher hatten wir zum Starten unserer Appliation den Runserver benutzt, den wir mit python manage.py runserver gestartet haben. Der Runserver ist für die Entwicklungsphase auf dem lokalen Rechner gedacht, nicht aber für den Produktivbetrieb. Die Gründe sind vielfältig: der Server ist nicht für hohen Traffic ausgelegt und es besteht auch nicht die Möglicheit, den Server zu tunen. Außerdem ist unsicher.

In einem Live-System betreiben wir nginx als Proxyserver, der die Last verteilt und den Input weiterroutet an den Webserver gunicorn.

eigener Server

Das Endprodukt kann natürlich auf einem eigenen Server betrieben werden. Dazu nötig ist ein Linux-Betriebsystem, Firewall, eine Datenbank und so weiter. Idealerweise läuft auf diesem System Docker, dann lässt sich der Docker-Container nutzen. Als Server dient nginx, der bei mehreren Docker-Containern auch gleich als Load-Balancer fungieren kann.

Allerdings ist das mit Aufwand verbunden.

Platform as a service

Einfacher geht’s mit Heroku. Der hochspezialisierte Platform-as-a-service-Dienst übernimmt die schwierigen Aufgaben, die Infrastrutkur in Gang zu halten, während sich der Entwickler nur noch um die Software kümmern muss.

Ein Tutorial dazu findet sich hier: https://realpython.com/django-hosting-on-heroku/

Fehler loggen mit Rollbar oder Sentry

Moderne Plattformen mit vielen Tausenden Usern produzieren Fehler. Um diese Fehler komfortabel auszuwerten, nutzen wir kommerzielle Error-Tracking-Dienste, die diese Aufgabe für uns übernehmen. Rollbar zum Beispiel bietet Gratis-Accounts für kleine websites an.

Projekt mit gunicorn starten

Um zu testen, ob das System soweit läuft, kann man es mit gunicorn starten.

gunicorn event_manager.wsgi