Docker-compose

Корпорация контейнеров: Docker-compose

В один образ можно упаковать простейшее приложение, работающее без базы данных и ни с чем не взаимодействующее, но на практике такие приложения встречаются редко. Обычно приложение состоит из нескольких сервисов — например, из Django-проекта, веб-сервера, базы данных и чего-нибудь ещё. Различные сервисы лучше распределять по отдельным контейнерам.
Отдельный контейнер для каждого сервиса позволит установить необходимые для него библиотеки, тогда как при установке этих библиотек в один контейнер велик шанс получить конфликты.
То же касается и настроек: в отдельных контейнерах можно настроить окружение каждого сервиса наилучшим для него образом без оглядки на то, как это отразится на остальных процессах.
Для управления взаимодействием нескольких контейнеров применяют утилиту docker-compose.
image

Окно в контейнере

Docker-контейнер запускается изолированно и никак не взаимодействует с «родительской» операционной системой и её процессами. Но какой смысл от приложения, если к нему нет доступа?
Контейнер может общаться с внешним миром через открытые порты. Для контейнера Yamdb, который вы уже создали и запустили, был настроен доступ через порт 8000: порт приложения, запущенного в контейнере, был «проброшен», соединён с внешним портом контейнера, и через этот порт можно обратиться к приложению. Так же, как и со внешним миром, между собой приложения из разных контейнеров могут обмениваться данными через порты контейнеров.

Контейнер на время, данные навсегда: Volumes

После того, как контейнер удалён — все его данные, накопленные за время работы, уничтожаются, и при следующем запуске образа работа начнётся «с чистого листа».
Это неудобно, а зачастую и недопустимо: будет плохо, если из блога пропадут все записи, а из магазина — все товары и информация о заказах. Нужно держать данные вне контейнера, но так, чтобы из контейнера был доступ к этим данным. Такой подход даст и дополнительный плюс: можно изменять данные, не заходя в контейнер.
Эту задачу в докере решает volume (в русских текстах его иногда называют «том», как «книжный том»). В самом общем смысле volume — это директория, доступная для контейнера.
Принцип работы volume можно сравнить с картой памяти, с «флешкой». На флешку с компьютера можно слить какие-то данные, и если затем компьютер будет выключен или даже уничтожен — данные на флешке сохранятся. То же и с volume: контейнер сохраняет данные в volume, и данные будут в сохранности вне зависимости от состояния контейнера.

Строим штабель из контейнеров

Контейнер, который вы создали, содержит приложение Yamdb и ничего более. Запустить это приложение можно только на встроенном в Django сервере разработчика.
Для полноценной работы проекта в докере надо настроить боевую систему так, как вы настраивали её на удалённом сервере: Django-проект с базой Postgres и сервер Gunicorn.

Подготовка Django-проекта

Для начала добавьте пакеты gunicorn (сервер) и psycopg2-binary (библиотека для работы с PostgreSQL) в файл requirements.txt: эти пакеты будут добавлены в образ при выполнении команды RUN pip install -r /code/requirements.txt в докерфайле.
Скопировать кодYAML
asgiref==3.2.10 Django==3.0.8 djangorestframework==3.11.0 djangorestframework-simplejwt==4.3.0 gunicorn==20.0.4 psycopg2-binary==2.8.5 PyJWT==1.7.1 pytz==2020.1 sqlparse==0.3.1
Секретные ключи, доступы, токены не следует хранить в коде. Но для работы приложения их нужно передать в контейнер. Это можно сделать несколькими способами:
  1. Прописать переменные окружения прямо в докерфайле, для этого есть инструкция ENV: ENV token 12345
  2. Можно задать переменные окружения при сборке контейнера, выполнив команду run с ключом -e: docker run <IMAGE-NAME> -e token=12345
  3. Можно создать файл .env и прописать переменные окружения в нём. Это самый предпочтительный способ.
Создайте файл .env с переменными окружения для работы с базой данных:
Скопировать кодYAML
DB_ENGINE=django.db.backends.postgresql # указываем, что работаем с postgresql DB_NAME=postgres # имя базы данных POSTGRES_USER=postgres # логин для подключения к базе данных POSTGRES_PASSWORD=postgres # пароль для подключения к БД (установите свой) DB_HOST=db # название сервиса (контейнера) DB_PORT=5432 # порт для подключения к БД
Измените файл settings.py, чтобы значения загружались из переменных окружения:
Скопировать кодYAML
DATABASES = { 'default': { 'ENGINE': os.environ.get('DB_ENGINE'), 'NAME': os.environ.get('DB_NAME'), 'USER': os.environ.get('POSTGRES_USER'), 'PASSWORD': os.environ.get('POSTGRES_PASSWORD'), 'HOST': os.environ.get('DB_HOST'), 'PORT': os.environ.get('DB_PORT'), } }

Подготовка докерфайла

Добавьте в ваш докерфайл команду для запуска приложения через gunicorn:
Скопировать кодYAML
FROM python:3.8.5 RUN mkdir /code COPY requirements.txt /code RUN pip install -r /code/requirements.txt COPY . /code WORKDIR /code CMD gunicorn api_yamdb.wsgi:application --bind 0.0.0.0:8000

Описание контейнеров: docker-compose.yaml

Инструкции по развёртыванию проекта в нескольких контейнерах пишут в файле docker-compose.yaml
В корневой директории проекта Yamdb создайте файл docker-compose.yaml:
Скопировать кодYAML
# версия docker-compose version: '3.8' # имя директории для хранения данных volumes: postgres_data: # имена и описания контейнеров, которые должны быть развёрнуты services: # описание контейнера db db: # образ, из которого должен быть запущен контейнер image: postgres:12.4 # volume и связанная с ним директория в контейнере volumes: - postgres_data:/var/lib/postgresql/data/ # адрес файла, где хранятся переменные окружения env_file: - ./.env web: build: . restart: always command: gunicorn api_yamdb.wsgi:application --bind 0.0.0.0:8000 ports: - "8000:8000" # "зависит от", depends_on: - db env_file: - ./.env
version — версия docker-compose, от версии зависит набор команд, которые можно применять в docker-compose.yaml. Подробнее о версиях можно прочесть в документации: https://docs.docker.com/compose/compose-file/compose-versioning/
volumes — имя директории для хранения данных.
services — список имён и описаний контейнеров, которые должны быть развёрнуты. В листинге описаны два контейнера: db и web.
В описании контейнера объявляется:
  • image — из какого образа создавать контейнер (как в описании контейнера db) или build <adress>: создать образ для контейнера из докерфайла, который лежит в директории <adress> (как в описании web).
  • volumes указывает, данные из какой директории контейнера нужно переносить в volume, во «внешнее хранилище данных» — и в какое именно (в проекте может быть несколько volume).
  • restart — аналог системы запуска юнитов в systemd.
  • command — аналог инструкции CMD в докерфайле: здесь пишется команда, которая должна быть выполнена после запуска контейнера.
  • env_file указывает, где лежат переменные окружения для проброса внутрь контейнера.
  • ports указывает, какие порты открыть наружу и какие порты приложения им соответствуют (это называют «проброс портов»).
  • depends_on определяет, после какого контейнера должен быть запущен описываемый контейнер. В листинге сказано, что контейнер web запустится после контейнера db.
Запустите docker-compose командой docker-compose up. У вас развернётся проект, запущенный через gunicorn с базой данных postgres.