Haikson

[ Everything is possible. Everything takes time. ]

Разворачиваем python проект с docker на примере django + nginx + gunicorn + postgresql

Целью этой статьи я не ставил объяснять, что такое docker. Так же, как и про его установку и настройку я не буду рассказывать. Если вы этого не знаете, то вам следует сначала хотя бы поверхностно ознакоится с нею и только потом вернуться к данной статье. Жаль, что я не смог раньше прийти к данному материалу, но, надеюсь, что после его прочтения вы сможете так же легко, как и я, разворачивать проекты для разработки с помощью docker.

На самом деле, даже понимая, как работает docker, мне было достаточно сложно освоить то, как на нем без боли и страданий поднять проект и спокойно разрабатывать. В силу не только обстоятельств, но и некоторых своих предпочтений на моей рабочей лошадке стои windows, что делает немного неудобным процесс разработки web приложений на python, и приходится прибегать к магии: сначала был virtualbox, потом (дай Бог ему долгой жизни) vagrant. Но когда захотелось проникнуться темой использования docker, я как то втянулся и пока счастливо живу с ним. Надеюсь и вам понравится, хотя в начале пути всех ждут трудности и разочарование.

Дальше будет меньше слов и больше интересного кода.

Docker-Compose

Да, именно он помог мне избавиться от боли в мозгу, когда я не мог склеить два контейнера. Именно он поможет нам быстро развернуть проект и запустить. Честно признаюсь, хоть я и не сторонник запуска с docker на production, но один сайт я все таки запустил. И он не просто работает, еще и заказчик доволен ).

Что нам дает docker-compose? С его помощью мы соберем контейнеры, прицепим дисковое пространство, установим необходимые пакеты и запустим все это. Про него можете почитать здесь, а потом продолжим историю.

Для начала же давайте определимся с файловой структурой. Здесь я приведу пример того, как мне удобно работать. Я уверен, что ты сообразительный малый и, если нужно, сам сообразишь что на что поменять и как все это улучшить. Основную директорию, где мы будем создавать все файлы и откуда будем все запускать, я кратко назову DEV_PATH. Это может быть C:/work/site1/ или /home/user/site2 или где то еще. Жирным выделяю названия директорий. Курсивом - файлов.

  • DEV_PATH
    • docker
      • ​nginx
        • sitename.conf
      • python
        • Dockerfile
    • <project> (Это директория с python проектом. Название проекта, соответственно, таким, как ты назовешь его. )
      • logs
      • <project>
        • settings.py
        • wsgi.py
      • gunicorn.py
      • requirements.txt
    • docker-compose.yml

Dockerfile

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

FROM python:3.6

COPY ./<project> /srv/www/<project>
WORKDIR /srv/www/<project>

RUN pip install -r requirements.txt

Построчно:

  1. Указываем имя контейнера, на основе кторого будем это все собирать. Я использую официальный контейнер под python 3.6. Ты можешь выбрать свою версию.
  2. Указываем что и куда мотировать. Я использую директорию /srv/www/имя_проекта для лаконичности. Важно понимать, что лучше указывать путь до проекта так, как он будет выглядеть на твоем сервере. Приближаем окружение к боевой системе.
  3. Указываем эту же директорию как точку входя при выполнении следующих команд.
  4. Запускаем установку пакетов из requirements.txt. После этого твой образ сохранится уже с этими пакетами и тебе не придется каждый раз пересобирать их. главное заранее подготовить файл со всеми нужными пакетами. Каждый раз, меняя requirements.txt, придется перезапускать сборку образа и, соответственно, с нуля устанавливать эти пакеты.

sitename.conf

Обычный файл конфигурации nginx. Он нужен для того, чтобы сразу после запуска nginx "скушал" наш проект.

# portal
server {
  listen 8080; # nginx будет слушать этот порт.
  server_name localhost;
  charset     utf8;
  autoindex   off;
  access_log  /srv/www/<project>/logs/<project>_access.log;
  error_log   /srv/www/<project>/logs/<project>_error.log error;
  set         $project_home /srv/www/<project>;


  location / {
    root $project_home;
    try_files $uri @<project>;
  }

  location @<project> {
    proxy_pass http://python:8000; # gunicorn запускается в контейнере python и слушает порт 8000
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
  }
}

На этом настройка nginx заканчивается, но сам образ еще будем ковырять.

gunicorn.py

Если нет желания запускать с ним, то, пожалуй, можете через manage.py runserver или иначе. Но мне gunicorn кажется весьма удачным решением. Вот конфигурационный файл.

from multiprocessing import cpu_count
from os import environ

def max_workers():
    return cpu_count()


bind = '0.0.0.0:' + environ.get('PORT', '8000')
max_requests = 1000
worker_class = 'gevent'
workers = max_workers()

env = {
    'DJANGO_SETTINGS_MODULE': '<project>.settings'
}

reload = True
name = 'Project_name'

не забываем в requirements.txt добавить gevent и gunicorn

docker-compose.yml

Наконец то дошли к самому сладкому. Приведу пример файла, а в нем все пояснения, чтобы мы не потеряли что для чего делалось.

version: '3'

# хранилища
volumes:
    pgdata:
        driver: local
services:
    nginx:
# при падении будет стараться подняться
        restart: always
# только свежий nginx
        image: nginx:latest
# слушает порт 8080
        expose:
          - 8080
# мапаем порт 80 на его 8080. Тогда сайт будет доступен по адресу localhost. Убедись, что порт у тебя не занят.
        ports:
          - "80:8080"
# монтируем только те директории, в которых лежит статика, т.к. nginx с динамикой не будет работать. Также директорию с логами и файл настройки, который мы подготовили.
        volumes:
            - ./<project>/static:/srv/www/<project>/static
            - ./<project>/media:/srv/www/<project>/media
            - ./<project>/logs:/srv/www/<project>/logs
            - ./docker/nginx:/etc/nginx/conf.d
# и nginx зависит от контейнера python. Т.е. python должен быть запущен первым
        depends_on:
            - python
    python:
        restart: always
# указываем откуда собирать образ
        build:
            context: .
            dockerfile: docker/python/Dockerfile
# монтируем директорию проекта
        volumes:
            - ./<project>:/srv/www/<project>
        expose:
          - 8000
        ports:
            - 8000:8000
# запускаем gunicorn
        command: "gunicorn -c gunicorn.py <project>.wsgi"
    postgres:
# Ниже даже расписывать не хочу, насколько все просто: логин, пароль, БД, порты и т.д.
        image: postgres:9.3.22
        ports:
            - 5432:5432
        environment:
            POSTGRES_USER: username
            POSTGRES_PASSWORD: postgresql_password
            POSTGRES_DB: database_name
            PGDATA: /var/lib/postgresql/data
        volumes:
            - pgdata:/var/lib/postgresql/data

Поехали

Ниже привожу только команды. Выполнять их нужно из директории, где лежит yml файл.

Сборка

docker-compose build

Запуск

docker-compose up -d

Остановка

docker-compose down

Не прощаясь

Надеюсь, что тебе поможет, если не понять как собирать сложные окружения, то, хотя бы как я, окружение для разработки стандартных проектов. Скоро расскажу как поднимать окружение для npm + gulp + bower с кучей сладких надстроек. Так что - возвращайся.