Docker-compose
Корпорация контейнеров: Docker-compose
В один образ можно упаковать простейшее приложение, работающее без базы данных и ни с чем не взаимодействующее, но на практике такие приложения встречаются редко. Обычно приложение состоит из нескольких сервисов — например, из Django-проекта, веб-сервера, базы данных и чего-нибудь ещё. Различные сервисы лучше распределять по отдельным контейнерам.
Отдельный контейнер для каждого сервиса позволит установить необходимые для него библиотеки, тогда как при установке этих библиотек в один контейнер велик шанс получить конфликты.
То же касается и настроек: в отдельных контейнерах можно настроить окружение каждого сервиса наилучшим для него образом без оглядки на то, как это отразится на остальных процессах.
Для управления взаимодействием нескольких контейнеров применяют утилиту docker-compose.
Окно в контейнере
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
Секретные ключи, доступы, токены не следует хранить в коде. Но для работы приложения их нужно передать в контейнер. Это можно сделать несколькими способами:
- Прописать переменные окружения прямо в докерфайле, для этого есть инструкция ENV:
ENV token 12345 - Можно задать переменные окружения при сборке контейнера, выполнив команду
run с ключом -e: docker run <IMAGE-NAME> -e token=12345 - Можно создать файл .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
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.