Используем Django, Postgresql и Nginx в контейнерах Docker


30 декабря 2020


Создание и запуск контейнера Docker с Django, PostgresSQL, Gunicorn и Nginx

В этой статье будет рассмотрена установка фреймворка Django, вместе с базой данных PostgreSQL, веб сервером Gunicorn и Nginx на контейнерах Docker с Compose. У вас уже должен быть установлен Docker и Compose. Так же, желательно, иметь хотя бы поверхностное представление о перечисленных технологиях. Так как в подобной настройке легко ошибиться - рекомендуется повторять все в точности.

В рамках статьи мы рассматриваем создание образа для локальной разработки и, с существующими наработками, создание образа для продакшена. На готовые образ, со всеми файлами и комментариями, можно посмотреть на GitHub.

Я буду использовать python3.8 для создания проекта на django 3+, но все операции (с оговорками в определенных местах) подойдут и для python 3.6+ и django 2+.

 

Создание проекта Django для тестирования

Для создания всех файлов проекта я создам директорию 'project'. Расположение этого каталога не имеет значения:

mkdir project
cd project

Что бы мы могли проверять работу приложений друг с другом - создадим тестовый проект Django. Вместе тестового проекта вы можете использовать ваш существующий, но тогда следите за наименованиями. Название каталога для Django будет 'app':

mkdir app
cd app

Во время создания проектов Python (в т.ч. Django) используется модель виртуального окружения. Виртуальное окружение помогает изолировать версии установленных пакетов и легко их переносить на другие компьютеры и проекты. Вообще этот модуль нужен только для конкретного тестового проекта и не будет переноситься в контейнер.

В Ubuntu, например, он устанавливается отдельно и называется 'python3-venv':

sudo apt install python3-venv

Следующей командой мы создадим окружение и активируем его:

python3 -m venv env
source env/bin/activate

Активирование виртуально окружения в Python

О том что виртуально окружение активировалось говорит строка '(env)' в начале командной строки. Если у вас остались вопросы, то почитайте статью по созданию виртуального окружения на Python.

Теперь установим Django через менеджер пакетов pip:

pip3 install django

Находясь в папке 'app' создадим проект с названием 'django_project' используя следующую команду:

django-admin.py startproject django_project .

Миграции Django - это процесс создания таблиц в базе данных. По умолчанию используется база SQLite. Учитывая, что мы будем использовать PostgreSQL вы можете их не проводить, но это способ проверки, что Django установлен и работает:

python manage.py migrate

Следующей командой мы запустим сервер Django:

python manage.py runserver

Проверка работы фреймворка Python Django

О том что Django работает говорить сообщение в скриншоте выше. Вы так же можете выполнить запрос или открыть страницу по адресу 'http://127.0.0.1:8000' через браузер или выполнить команду:

curl http://127.0.0.1:8000

Выполнение запроса через curl

Для того что бы мы могли переносить версии пакетов созданные в виртуальном окружении создадим файл 'requirements.txt' следующим образом:

pip3 freeze > requirements.txt

Так же рекомендую сравнить структуру папок, которая должна быть у вас на этом моменте.

Структура проекта для Django на Docker

 

Создание контейнера Django

Находясь в папке 'app' создадим файл контейнера с названием Dockerfile. Поместим в него следующее содержимое:

vim Dockerfile
# образ на основе которого создаём контейнер
FROM python:3.8.6-alpine

# рабочая директория внутри проекта
WORKDIR /usr/src/app

# переменные окружения для python
ENV PYTHONDONTWRITEBYTECODE 1
ENV PYTHONUNBUFFERED 1

# устанавливаем зависимости
RUN pip install --upgrade pip
COPY ./requirements.txt .
RUN pip install -r requirements.txt

# копируем содержимое текущей папки в контейнер
COPY . .

Обратите внимание на следующие вещи:

  1. Вы можете использовать другую версию Python. Репозиторий образов находится тут;
  2. Переменная окружения 'PYTHONDONTWRITEBYTECODE' - Python не будет создавать файлы кэша .pyc;
  3. 'PYTHONUNBUFFERED' - не помещает в буфер потоки stdout и stderr.

Теперь перейдем на уровень выше (в папку 'project/') и создадим в ней файл 'docker-compose.yml' со следующим содержимым:

cd ..
vim docker-compose.yml
version: '3.8'

services:
  web:
    # Берем Dockerfile из каталога app
    build: ./app
    # Запускаем тестовый сервер
    command: python manage.py runserver 0.0.0.0:8000
    # куда будут помещены данные из каталога app
    volumes:
      - ./app/:/usr/src/app/
    # Открываем порт 8000 внутри и снаружи
    ports:
      - 8000:8000
    # Файл содержащий переменные для контейнера
    env_file:
      - ./.env.dev

Обратите внимание на значение 'version'. Если вы используете старую версию Docker - вам может потребоваться ее изменить.

В самом конце файла мы разместили значение 'env_file', который еще не создали. В зависимости от того как вы ведете проект на Django - вы можете организовать процесс иначе чем описано далее. В этом файле будут размещены переменные, которые по разным причинам могут быть изменены. Это, например, может быть переменная с паролем, которая для каждого проекта будет принимать разное значение.

Нам нужно вернуться в каталог 'app', где находится django, и изменить следующие значения в файле 'settings.py':

  • SECRET_KEY - ключ 'безопасности' через который работают такие вещи как csrf_token.
  • DEBUG - будет ли сервер отображать ошибки или просто выводить 404;
  • ALLOWED_HOSTS - с каких хостов можно будет подключаться к Django.

Мы по-разному определим значения этих переменных для разных версий Django. Версию вашего Django можно посмотреть, например, так:

cat app/requirements.txt

Файл requirements.txt

Откроем файл settings.py:

vim app/django_project/settings.py

Если версия Django меньше или равна 3, то в settings.py уже используется библиотека os и вы должны будете заменить значения следующих переменных так:

SECRET_KEY = os.environ.get('SECRET_KEY')

DEBUG = int(os.environ.get('DEBUG', default=0))

ALLOWED_HOSTS = os.environ.get('ALLOWED_HOSTS').split(' ')

Начиная с версии Django 3.1 вместо os используется библиотека pathlib и нам нужно выполнить дополнительный импорт:

from os import environ

SECRET_KEY = environ.get('SECRET_KEY')

DEBUG = int(environ.get('DEBUG', default=0))

ALLOWED_HOSTS = environ.get('ALLOWED_HOSTS').split(' ')

Определение глобальных переменных в Django 3.1

Теперь, в корне проекта, каталоге 'project', создадим файл '.env.dev' со следующим содержимым:

vim .env.dev
DEBUG=1
SECRET_KEY=secretkey
ALLOWED_HOSTS=localhost 127.0.0.1

Настройки в файле env.dev говорят, что сервер будет запускаться в режиме "отладки" и принимать подключения с 127.0.0.1 или localhost.

Теперь, каждый раз выполняя сборку контейнеров, у нас будут выполняться следующий процесс:

  1. При запуске контейнера будет читаться файл .env.dev, содержащий какие-то значения;
  2. Значения из файла будут помещены как глобальные переменные проекта (переменные окружения);
  3. Django, через библиотеку os.eviron, читает некоторые переменные из окружения и подставляет где нужно.

Таким образом, если мы захотим поменять настройки, нам не придется еще раз редактировать 'settings.py' или 'docker-compose.yml'.

Теперь выполним билд проекта (из папки 'project'), при котором создастся нужный контейнер:

docker-compose build

Запустим контейнер:

docker-compose up -d

Запуск контейнеров на Django

Проверьте открывается ли страница по адресу 'http://localhost:8000/' или можете выполнить команду:

curl http://localhost:8000

Выполнение тестового запроса через curl к localhost

Если у вас какие-то ошибки - проверьте соответствует ли у вас структура папок:

Структура проекта для запуска Django с PostgreSQL на Docker

Дополнительно вы можете посмотреть на вывод логов докера:

docker-compose logs -f

 

PostgreSQL

Что бы мы могли работать с PostgreSQL нам нужно создать пользователя и базу данных, а так же установить некоторые зависимости для самого Django. За счет них мы сможем выполнять подключения к базе и выполнять запросы.

Для создания базы данных мы обновим файл docker-compose.yml добавив блок под названием db. В этом блоке мы пропишем учетные данные пользователя и имя базы (образ Postgres подхватывает эти переменные сам) и том, на котором будем хранить базу. Обратите внимание на версию образа postgres, возможно вы захотите ее изменить:

vim docker-compose.yml
version: '3.8'

services:
  web:
    build: ./app
    command: python manage.py runserver 0.0.0.0:8000
    volumes:
      - ./app/:/usr/src/app/
    ports:
      - 8000:8000
    env_file:
      - ./.env.dev
  db:
    # Образ и версия базы, которую мы будем использовать
    image: postgres:12.0-alpine
    # Внешний том(volume) и место где он будет подключен внутри контейнера
    volumes:
      - postgres_volume:/var/lib/postgresql/data/
    environment:
      # Учетные данные, которые можно будет переопределить
      - POSTGRES_USER=django_user
      - POSTGRES_PASSWORD=django_password
      - POSTGRES_DB=django_db

volumes:
  postgres_volume:

Обычно в контейнерах базы данных не запускают (исключения локальная разработка и тесты). Если вам нужно запускать базу в контейнере, то скорее всего вы будете делать это не единожды. В обычном случае, если не описывать volume для контейнера с базой, каждое пересоздание контейнера приведет к уничтожению базы и данных. В случае выше мы описали создание внешнего тома Docker 'postgres_volume', который будет хранить данные на случай пересоздания контейнера.

Django тоже нужно знать какой логин и пароль, а так же имя базы, использует Postgres. Что бы мы этим могли удобно управлять - обновим файл где храним переменные окружения:

vim .env.dev
DEBUG=1
SECRET_KEY=foo
ALLOWED_HOSTS=localhost 127.0.0.1 [::1]
POSTGRES_ENGINE=django.db.backends.postgresql
POSTGRES_DB=django_db
POSTGRES_USER=django_user
POSTGRES_PASSWORD=django_password
POSTGRES_HOST=db
POSTGRES_PORT=5432

На данные момент получается, что в 'docker-compose.yml' мы используем логин и пароль, а так же и файле переменных для Django. Если вам удобно, то вы можете создать отдельный файл переменных для базы данных или использовать один '.env.dev' для двоих контейнеров.

Нам еще раз нужно отредактировать файл 'setting.py', и изменить значения переменной 'DATABASES', хранящие настройки для баз данных:

vim app/django_project/settings.py

Если вы используете Django 3.0 или меньше 

DATABASES = {
    'default': {
        'ENGINE': os.environ.get('POSTGRES_ENGINE', 'django.db.backends.sqlite3'),
        'NAME': os.environ.get('POSTGRES_DB', os.path.join(BASE_DIR, 'db.sqlite3')),
        'USER': os.environ.get('POSTGRES_USER', 'user'),
        'PASSWORD': os.environ.get('POSTGRES_PASSWORD', 'password'),
        'HOST': os.environ.get('POSTGRES_HOST', 'localhost'),
        'PORT': os.environ.get('POSTGRES_PORT', '5432'),
    }
}

Если у вас Django 3.1 или больше:

DATABASES = {
    'default': {
        'ENGINE': environ.get('POSTGRES_ENGINE', 'django.db.backends.sqlite3'),
        'NAME': environ.get('POSTGRES_DB', BASE_DIR / 'db.sqlite3'),
        'USER': environ.get('POSTGRES_USER', 'user'),
        'PASSWORD': environ.get('POSTGRES_PASSWORD', 'password'),
        'HOST': environ.get('POSTGRES_HOST', 'localhost'),
        'PORT': environ.get('POSTGRES_PORT', '5432'),
    }
}

Настройка Django для работы на Docker с PostgreSQL

Установка зависимостей, которые требуются для работы Django с Postgres, выполняется в двух местах. Первое - через менеджер пакетов контейнера мы установим 'postgresql-dev', 'gcc', 'python3-dev' и 'musl-dev':

vim app/Dockerfile
FROM python:3.8.6-alpine

# рабочая директория внутри проекта
WORKDIR /usr/src/app

# переменные окружения
ENV PYTHONDONTWRITEBYTECODE 1
ENV PYTHONUNBUFFERED 1

# Устанавливаем зависимости для Postgre
RUN apk update \
    && apk add postgresql-dev gcc python3-dev musl-dev

# устанавливаем зависимости
RUN pip install --upgrade pip
COPY ./requirements.txt .
RUN pip install -r requirements.txt

# копируем содержимое текущей папки в контейнер
COPY . .

Установка зависимостей для Django и PostgreSQL на Docker

Вторым шагом мы должны воспользоваться менеджером пакетов для Python - pip, который установит библиотеку 'psycopg2-binary'. Эта библиотека, в дальнейшем, должна быть добавлена в файл 'requirements.txt'.

Вы можете установить библиотеку правильным способом, через pip и виртуальное окружение:

source app/env/bin/activate
pip install psycopg2-binary
pip freeze > app/requirements.txt

Или не очень правильным, добавив жесткую версию библиотеки, как в команде ниже:

echo "psycopg2-binary==2.8.6" >> app/requirements.txt

Создание файла requirements.txt через pip

Проблема прописывания версии напрямую в том, что у вас может быть проблема с зависимостями.

Выполним билд и запуск наших контейнеров еще раз:

docker-compose up -d --build

У вас не должно появляться ошибок. Вы можете открыть страницу 'http://127.0.0.1' или выполнить запрос curl для проверки работы Django:

curl localhost:8000

Выполнение запроса к Django

Теперь проверим работу подключения к базе данных. Выполним миграции при которых будут созданы таблицы самим Django:

docker-compose exec web python manage.py migrate --noinput

Выполнение миграций для Django и PostgreSQL на Docker

Мы можем подключиться к контейнеру с базой данных и посмотреть созданные базы, пользователей и таблицы. Замените пользователя и название базы если использовали другие:

docker-compose exec db psql --username=django_user --dbname=django_db

Затем, последовательно, выполните следующие команды:

\l
\dt

Просмотр созданных таблиц и баз на PostgreSQL

У нас вывелась база для джанго (2) и список созданных через миграцию таблиц (1). Выйдем из режима работы с базой:

\q

Проверить, что у нас был создан том для базы можно так:

docker volume ls
docker volume inspect project_postgres_volume

Просмотр созданного тома через docker-compose

Дополнительные настройки для PostgreSQL

Мы можем создать скрипт, который будет проверять запущен ли сервер с базой, дожидаться этого ответа и выполнять миграции автоматически. Для этого мы создадим файл entrypoint.sh по следующему пути:

vim app/entrypoint.sh

Поместите в него следующий текст:

#!/bin/sh

if [ "$DATABASE" = "postgres" ]
then
    # если база еще не запущена
    echo "DB not yet run..."

    # Проверяем доступность хоста и порта
    while ! nc -z $POSTGRES_HOST $POSTGRES_PORT; do
      sleep 0.1
    done

    echo "DB did run."
fi
# Удаляем все старые данные
python manage.py flush --no-input
# Выполняем миграции
python manage.py migrate

exec "$@"

Обратите внимание на то, что у вас может получится вечный цикл, если контейнер с postgres не запустится вообще. Условие, проверяющее является ли база 'postgres', нужно если вы захотите создать отдельный контейнер без использования PostgreSQL.

Для работы этого скрипта нам изменить права на файл:

chmod 774 app/entrypoint.sh

Так же изменим Dockerfile и добавим запуск этого скрипта при запуске контейнера:

echo 'ENTRYPOINT ["/usr/src/app/entrypoint.sh"]' >> app/Dockerfile

Создание Dockerfile для проекта Django

Не забудьте добавить переменную DATABASE в файл с переменными:

echo 'DATABASE=postgres' >> .env.dev

Создание файла с пакетами Python requirements.txt

Перед тестированием шагов выше - остановите контейнеры и удалите том, который был создан для базы раннее:

docker-compose down
docker volume ls
docker volume rm project_postgres_volume

Создадим образы и запустим контейнеры: 

docker-compose up -d --build

Миграции должны выполнится автоматически:

docker-compose logs

Просмотр логов миграций через Docker

Если вы получили ошибку (показанную ниже) или не увидели миграций, то скорее всего это связано с тем что вы не удалили предыдущий том:

  • FATAL:  database "django_database" does not exist

 

Gunicorn

Для работы между Django и Nginx используется gunicorn. В момент запроса пользователя, в первую очередь, он будет обработан Nginx. Nginx решит, к какому типу относится запрос (статический/динамический) и в случае динамического содержимого обратиться к Gunicorn.

Сам Gunicorn, так же как и библиотеку psycopg2 нужно установить и добавить в 'requerements.txt' двумя путями.

Рекомендованный способ - через виртуальное окружение и pip:

source app/env/bin/activate
pip install gunicorn
pip freeze > app/requirements.txt

Установка gunicorn

Второй способ - просто добавьте имя и версию библиотеки без установки:

echo 'gunicorn==20.0.4' >> app/requirements.txt

Сам gunicorn, чаще всего, будет использоваться в продакшене. Что бы было удобно использовать образ для локальной разработки и продакшена создадим второй файл с названием 'docker-compose.prod.yml'. Он будет аналогичен предыдущему образу за исключением отмеченных моментов:

vim docker-compose.prod.yml
version: '3.8'

services:
  web:
    # Берем Dockerfile из каталога app
    build: ./app
    # Запускаем сервер gunicorn
    command: gunicorn django_project.wsgi:application --bind 0.0.0.0:8000
    # Открываем порт 8000 внутри и снаружи
    ports:
      - 8000:8000
    env_file:
      - ./.env.prod
    # Дожидаемся запуска контейнера db
    depends_on:
      - db
  db:
    # Образ и версия базы, которую мы будем использовать
    image: postgres:12.0-alpine
    # Внешний том(volume) и место где он будет подключен внутри контейнера
    volumes:
      - postgres_volume:/var/lib/postgresql/data/
    env_file:
      - ./.env.prod.db

volumes:
  postgres_volume:

Создание docker-compose.yml для Django и PostgreSQL

Где:

  1. Мы запускаем через gunicorn наш wsgi.py(создается автоматически) файл в папке django_project;
  2. Новый файл, который будет хранить наши переменные для рабочей среды;
  3. Дожидаемся загрузки базы;
  4. Переменные для базы данных.

Мы так же убрали строку, где пробрасывали содержимое папки app в контейнер т.к. это не понадобится.

В файл '.env.prod' поместим переменные, аналогичные 'env.dev. Подразумевается, что для локально и для продакшена вы используете разные переменные:

cp .env.dev .env.prod

Если раннее вы не создавали файл с переменными для баз данных - то создайте. Или выполните копирование:

cp .env.dev.db .env.prod.db

Содержимое этого файла должно быть таким:

vim .env.prod.db
POSTGRES_USER=django_user
POSTGRES_PASSWORD=django_password
POSTGRES_DB=django_db

Выполним остановку предыдущих контейнеров и билд нового:

docker-compose down -v
docker-compose -f docker-compose.prod.yml up -d --build

Все тесты должны проходить успешно.

На данный момент структура созданных файлов и папок следующая:

Структура проекта для Django и Docker

 

Создание Dockerfile для продакшена

Ранее, когда мы создавали скрипт для проверки базы и миграций, мы выполняли метод flush, который очищал базу. Что бы этого не происходило, а так же что бы миграции не накатывались при каждом запуске контейнера - создадим файл 'entrypoint.prod.sh' со следующим содержимым:

vim app/entrypoint.prod.sh
#!/bin/sh

if [ "$DATABASE" = "postgres" ]
then
    echo "Postgres еще не запущен..."

    # Проверяем доступность хоста и порта
    while ! nc -z $POSTGRES_HOST $POSTGRES_PORT; do
      sleep 0.1
    done

    echo "Postgres запущен"
fi

exec "$@"

Изменим разрешение на файл:

chmod 774 app/entrypoint.prod.sh

Теперь создадим образ докера для создания контейнера с Django. В примере ниже будет создан мульти-образ для экономии месте. Builder - это временный образ с помощью которого будут созданы бинарные файлы Python. После создания образа builder с него будут скопированы файлы в наш основной образ:

vim app/Dockerfile.prod
###########
# BUILDER #
###########

FROM python:3.8.3-alpine as builder

WORKDIR /usr/src/app

ENV PYTHONDONTWRITEBYTECODE 1
ENV PYTHONUNBUFFERED 1

# установка зависимостей
RUN apk update \
    && apk add postgresql-dev gcc python3-dev musl-dev
RUN pip install --upgrade pip

# проверка кода через линтер
RUN pip install flake8
COPY . .
RUN flake8 --ignore=E501,F401 /usr/src/app/django_project

# установка зависимостей
COPY ./requirements.txt .
RUN pip wheel --no-cache-dir --no-deps --wheel-dir /usr/src/app/wheels -r requirements.txt


#########
# FINAL #
#########

FROM python:3.8.3-alpine

# создаем директорию для пользователя
RUN mkdir -p /home/app

# создаем отдельного пользователя
RUN addgroup -S app && adduser -S app -G app

# создание каталога для приложения
ENV HOME=/home/app
ENV APP_HOME=/home/app/web
RUN mkdir $APP_HOME
WORKDIR $APP_HOME

# установка зависимостей и копирование из builder
RUN apk update && apk add libpq
COPY --from=builder /usr/src/app/wheels /wheels
COPY --from=builder /usr/src/app/requirements.txt .
RUN pip install --no-cache /wheels/*

# копирование entrypoint-prod.sh
COPY ./entrypoint.prod.sh $APP_HOME

# копирование проекта Django
COPY . $APP_HOME

# изменение прав для пользователя app
RUN chown -R app:app $APP_HOME

# изменение рабочего пользователя
USER app

ENTRYPOINT ["/home/app/web/entrypoint.prod.sh"]

В образе мы так же создаем пользователя и группу app. Это делается для того, что бы не использовать пользователя 'root' , который используется по умолчанию Docker.

Так же обратите внимание на строку с flake8. С помощью этого пакета будет выполнятся проверка вашего приложения и в случае ошибок в стиле кода - билд не будет выполнен. Возможно вы захотите убрать эту строку.

Изменим файл 'docker-compose.prod.yml' что бы он использовал новый файл Docker:

vim docker-compose.prod.yml
version: '3.8'

services:
  web:
    # Берем Dockerfile из каталога app
    build:
       context: ./app
       dockerfile: Dockerfile.prod
    # Запускаем сервер gunicorn
    command: gunicorn django_project.wsgi:application --bind 0.0.0.0:8000
    # Открываем порт 8000 внутри и снаружи
    ports:
      - 8000:8000
    env_file:
      - ./.env.prod
    # Дожидаемся запуска db
    depends_on:
      - db
  db:
    # Образ и версия базы, которую мы будем использовать
    image: postgres:12.0-alpine
    # Внешний том(volume) и место где он будет подключен внутри контейнера
    volumes:
      - postgres_volume:/var/lib/postgresql/data/
    env_file:
      - ./.env.prod.db

volumes:
  postgres_volume:

Билд образа Django на Docker

Протестируем:

docker-compose -f docker-compose.prod.yml down -v
docker-compose -f docker-compose.prod.yml up -d --build
docker-compose -f docker-compose.prod.yml exec web python manage.py migrate --noinput

Выполнение миграций через docker exec

 

Nginx

Nginx будет работать со статическими файлами и будет обращаться к Gunicorn. Первое, что мы сделаем - добавим следующие строки в файл 'docker-compose.prod.yml':

vim docker-compose.prod.yml
nginx:
  build: ./nginx
  ports:
    - 1337:80
  depends_on:
    - web

Настройка Nginx для Django на Docker

В примере выше мы открываем порт 1337 для посетителей сайта, который будет перенаправлять пакеты на 80 порт nginx. Вы можете изменить порт 1337 на любой другой.

Создадим папку, в которой будут хранится конфигурационный файл nginx и файл Docker:

mkdir nginx

Создадим файл конфигурации Nginx со следующим содержимым:

vim nginx/nginx.conf
upstream django_proj {
    # Список бэкэнд серверов для проксирования
    server web:8000;
}

server {

    listen 80;

    # Параметры проксирования
    location / {
        # Если будет открыта корневая страница
        # все запросу пойдут к одному из серверов
        # в upstream django_proj
        proxy_pass http://django_proj;
        # Устанавливаем заголовки
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header Host $host;
        # Отключаем перенаправление
        proxy_redirect off;
    }

}

Что бы файл конфигурации попал в контейнер мы должны создать отдельный Dockerfile:

vim nginx/Dockerfile
FROM nginx

RUN rm /etc/nginx/conf.d/default.conf
COPY nginx.conf /etc/nginx/conf.d/

После создания образа Nginx мы уже не будем обращаться к Django напрямую, а это значит что мы можем закрыть для него доступ через перенаправление портов. Мы можем разрешить обращаться к Django только сервисам Docker. Для этого заменим 'port' на 'expose':

vim docker-compose.prod.yml
expose:
  - 8000

Открытие внутреннего порта на Docker

Запустим контейнеры еще раз и убедимся, что они работают:

docker-compose -f docker-compose.prod.yml down -v
docker-compose -f docker-compose.prod.yml up -d --build
docker-compose -f docker-compose.prod.yml exec web python manage.py migrate --noinput

Выполнение миграций для Django

Теперь у нас будет отвечать не Django, а nginx:

curl localhost:8000
curl localhost:1337

Тестирование работы Django с Nginx

На данный момент у вас должна быть следующая структура папок и файлов:

Структура проекта для Docker с Django, Nginx и PostgreSQL

 

Настройка для статических и медиа файлов

Для работы с файлами, которые не изменяются Django, используется понятие статических файлов. Так же есть понятие медиа файлов, которые определяют файлы загруженные через Django. Настройки для этих файлов выполняются в 'settings.py'. Нам нужно определить некоторые переменные для разных версий Django:

vim app/django_project/settings.py

Для Django 3.0 и меньше:

# То как статический файл будет отображаться в url
# Пример /static/1.jpg
STATIC_URL = "/static/"
# По какому пути можно будет найти файлы
STATIC_ROOT = os.path.join(BASE_DIR, "static")

# Аналогично static файлам
MEDIA_URL = "/media/"
MEDIA_ROOT = os.path.join(BASE_DIR, "media")

Для Django 3.1 и больше:

# То как статический файл будет отображаться в url
# Пример /static/1.jpg
STATIC_URL = "/static/"
# По какому пути можно будет найти файлы
STATIC_ROOT = BASE_DIR / "static"

# Аналогично static файлам
MEDIA_URL = "/media/"
MEDIA_ROOT = BASE_DIR / "media"

Настройка медиа и статических файлов для Django

Обратите внимание, что у вас уже будет прописан 'STATIC_URL' самим Django и вы должны избежать дублирования этой переменной. Эта переменная будет иметь значение аналогичное написанному выше.

Эти папки будут работать в контейнерах по пути '/home/app/web/static' и /home/app/web/media'. Так как эти файлы могут быть использованы как Nginx, так и Django мы создадим 2 тома Docker и подключим их к обоим контейнерам:

vim docker-compose.prod.yml
version: '3.8'

services:
  web:
    build:
       context: ./app
       dockerfile: Dockerfile.prod
    command: gunicorn django_project.wsgi:application --bind 0.0.0.0:8000
    # Открываем порт 8000 наружу
    expose:
      - 8000
    # Подключаем статические и медиа файлы
    volumes:
      - static_volume:/home/app/web/static
      - media_volume:/home/app/web/media
    env_file:
      - ./.env.prod
    # Дожидаемся запуска db
    depends_on:
      - db
  db:
    image: postgres:12.0-alpine
    volumes:
      - postgres_volume:/var/lib/postgresql/data/
    env_file:
      - ./.env.prod.db
  nginx:
    build: ./nginx
    # Подключаем статические и медиа файлы
    volumes:
      - static_volume:/home/app/web/static
      - media_volume:/home/app/web/media
    ports:
      - 1337:80
    depends_on:
      - web

volumes:
  postgres_volume:
  static_volume:
  media_volume:

Чтобы у нас не было проблем с правами (эти папки будут проброшены с правами для root) мы должны создать аналогичные папки в контейнере используя Dockerfile.prod. Допишите следующие инструкции в следующем месте:

vim app/Dockerfile.prod
RUN mkdir $APP_HOME/static
RUN mkdir $APP_HOME/media

Создание папок и пользователя для Django в Docker контейнере

Так же нужно изменить файл конфигурации Nginx что он тоже обращался к этим папкам:

vim nginx/nginx.conf
upstream django_proj {
    server web:8000;
}

server {

    listen 80;

    location / {
        proxy_pass http://django_proj;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header Host $host;
        proxy_redirect off;
    }
    # подключаем статические файлы
    location /static/ {
        alias /home/app/web/static/;
    }
    # подключаем медиа файлы
    location /media/ {
        alias /home/app/web/media/;
   }
}

 

Тестирование работы образов и возможные ошибки

Выполним остановку предыдущих контейнеров и создадим новые. При этом выполним миграции и соберем все статические файлы:

docker-compose down -v
docker-compose -f docker-compose.prod.yml up -d --build
docker-compose -f docker-compose.prod.yml exec web python manage.py migrate --noinput
docker-compose -f docker-compose.prod.yml exec web python manage.py collectstatic --no-input --clear

В файле 'docker-compose.prod.yml' мы прописали что nginx будет работать на 1337 порту. Проверим, что сервер отвечает:

curl localhost:1337

Тестирование работы nginx

Я могу открыть сайт с любого компьютера в локальной сети т.к. в файле '.env.prod' прописано несколько хостов с которых может быть принято соединение:

ALLOWED_HOSTS=localhost 127.0.0.1 192.168.2.121 [::1]

Главная страница Django

Если у вас будет появляться ошибка с 'ALLOWED_HOSTS', то вы можете установить следующее значение разрешающее все подключения с любого адреса:

ALLOWED_HOSTS=*

Один из способов проверки работы статических файлов - это открытие панель администрирования по адресу 'http://localhost/admin/'. Фрагмент, помеченный цифрой 1, говорит об ошибке. Фрагмент под цифрой 2 - нормальная работа:

Ошибка со статическими файлами Django

Нам нужно убедиться, что везде верно прописаны настройки для статических файлов и следующая команда, которая собирает такие файлы воедино, выполнена:

docker-compose -f docker-compose.prod.yml exec web python manage.py collectstatic --no-input --clear

Эти файлы должны отображаться на обоих контейнерах т.к. они работают с одним томом:

docker exec project_nginx_1 ls /home/app/web/static
docker exec project_web_1 ls /home/app/web/static

Сбор и просмотр статических файлов Django с collectstatic

Если файлов нет - возможно у вас проблемы с правами. Сравните файл Docker. Если нет таких папок, то с томом в файле docker-compose.prod.yml. Ошибка так же может быть в файле конфигурации nginx.conf или в файле setting.py Django.

Для проверки медиа файлов, которые будут загружаться через Django, вы можете создать пустой файл:

docker exec project_nginx_1 touch /home/app/web/media/test.txt

Далее выполнить к нему запрос:

curl localhost:1337/media/test.txt

Если вернется ошибка 404 - то стоит посмотреть на те же места, что в случае статических файлов.

Чаще всего помогает просмотр логов через докер:

docker-compose logs

В некоторых случаях стоит удалить том с базой, который создавался ранее.

 

Варианты запуска

У вас есть 2 основных файла Docker:

  • docker-compose.yml - для локальной разработки. Запускает только Django и Postgres. В случае этого запуска база данных будет очищена (файл 'app/entrypoint.sh');
  • docker-compose.prod.yml - для запуска на релизных версиях. Запускает Django, Postgres, Nginx и Gunicorn. Номер порта, для Nginx, указан в самом файле (1337).

В каждом файле docker-compose используются файлы, с переменными окружений, которые начинаются на '.env'. В случае локального образа - это один файл (переменные для Postgres прописаны в docker-compose.yml), в случае продакшена это два файла. Все файлы имеют одинаковые переменные, значение которых можно изменить:

  • DEBUG - принимает значение от 0 до 1. В случае 1 будут выводится сообщения об ошибках при открытии страниц сайта;
  • SECRET_KEY - ключ безопасности, который чаще всего имеет разное значение в случае локального запуска и продакшена;
  • ALLOWED_HOSTS - имена или ip адреса хостов через пробел, через которые можно подключиться к Django;
  • POSTGRES_ENGINE - модуль для работы с базой. Обычно не изменяется;
  • POSTGRES_DB - название базы, которую хотите использовать;
  • POSTGRES_USER - пользователь;
  • POSTGRES_PASSWORD - пароль;
  • POSTGRES_HOST - название контейнера (используется db);
  • POSTGRES_PORT - порт;
  • DATABASE - если значение не равно postgres не будет осуществлена проверка работы базы данных.

Для запуска образа для разработки выполните:

docker-compose up -d --build

В случае образа для релиза выполните:

docker-compose -f docker-compose.prod.yml up -d --build

В отличие от обычного образа, в случае продакшена нужно дополнительно выполнять миграции и сбор статических файлов:

docker-compose -f docker-compose.prod.yml exec web python manage.py migrate --noinput
docker-compose -f docker-compose.prod.yml exec web python manage.py collectstatic --no-input --clear 

 

...

Теги: #django #gunicorn #docker #nginx


Каналы
Telegram FixMyPc Telegram Лента FixMyPC RSS Rss
Популярные тэги
О блоге
Этот блог представляет собой конспекты выученного материала, преобретенного опыта и лучшие практики в системном администрировании и программировании.