Виртуализация процесса разработки, часть 1: Docker

Привет, меня зовут Андрей Двояк. Я специалист по комплексной разработке веб-приложений в украинском стартапе Preply.com, это платформа для поиска репетиторов. За последний год наша команда выросла, и для облегчения процесса адаптации новых разработчиков мы решили организовать и стандартизировать наш процесс разработки.

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

В этом руководстве я покажу вам, как «докеризировать» ваше приложение, чтобы вы таким образом могли удобно и просто распространить и развернуть его на любой машине, поддерживающей Docker.

Прежде, чем начать

В этом руководстве мы будем работать с простым приложением Django с базой PostgerSQL и Redis в качестве брокера для выполнения задач Celery. Также мы используем Supervisor для запуска нашего сервера Gunicorn. Мы будем использовать технологию Docker Compose, чтобы организовать работу нашего мультиконтейнерного приложения. Обратите внимание, что Compose 1.5.1 требует Docker 1.8.0 или более поздние версии.

Это поможет нам запустить приложение Django, PostgreSQL и Redis Server и Celery Worker в отдельных контейнерах и связать их между собой. Чтобы реализовать это все, нам нужно всего лишь создать несколько файлов в корневом каталоге вашего проекта Django рядом с файлом manage.py):

  1. Dockerfile — для создания финального образа и загрузки его в DockerHub;
  2. redeploy.sh — для развертывание в обеих средах DEV и PROD;
  3. docker-compose.yml — организовать работу нескольких контейнеров;
  4. Vagrantfile — предоставить виртуальную машину для среды разработки.

Мы используем переменную RUN_ENV для определения текущей среды. Вы набираете на клавиатуре export RUN_ENV=PROD на рабочем сервере и export RUN_ENV=DEV на виртуальной машине Vagrant.

Docker

Как вы, возможно, уже знаете, о Docker нужно знать две вещи: образы и контейнеры. Мы создадим образ, основанный на Ubuntu с установленными Python, PIP и другими инструментами, необходимыми для запуска вашего приложения Django. В этом образе будет предустановлено всё, что необходимо. Этот образ направим в публичное хранилище в DockerHub. Имейте ввиду, что этот образ не содержит ни одного файла вашего проекта.

Мы договорились хранить наш образ на DockerHub всегда обновленным. С этого образа мы загрузим контейнер, пробрасывая специфические порты и монтируя ваш локальный каталог с проектом к какой-то папке внутри контейнера. Это означает, что файлы вашего проекта будут доступны внутри контейнера. Никакой необходимости копировать файлы! Это очень удобно для процесса разработки, потому что вы можете вносить изменения в ваши файлы, и они сразу же будут изменены в работающем контейнере Docker, но это неприемлемо в реальной рабочей среде.

Как только вы запустили контейнер, мы запустим Supervisor с вашим сервером Gunicorn. Как только вы захотели сделать повторное развертывание, мы извлечем новые данные из GitHub, остановим и удалим существующий контейнер и запустим совершенно новый контейнер. Настоящее волшебство!

Установка Docker и Docker-Compose

Прежде чем начать, нам нужно установить Docker и Docker-Compose на наш локальный компьютер или сервер. Ниже указан скрипт, который это делает на Ubuntu 14.04:

sudo -i
echo 'debconf debconf/frontend select Noninteractive' | debconf-set-selections
curl -sSL https://get.docker.com/ | sh
curl -L https://github.com/docker/compose/releases/download/1.5.1/docker-compose-`uname -s`-`uname -m` > /usr/local/bin/docker-compose
chmod +x /usr/local/bin/docker-compose
usermod -aG docker ubuntu
sudo reboot

Тут ubuntu — ваш текущий пользователь.

Если ваша текущая среда разработки не Ubuntu 14.04 — тогда вам будет лучше использовать Vagrant для создания этой среды. Подробнее расскажу в следующей статье.

Образ Docker

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

Давайте создадим новый докер файл под названием Dockerfile в корневом каталоге проекта. Это будет выглядеть так:

FROM ubuntu:14.04
MAINTAINER Andrii Dvoiak
RUN echo 'debconf debconf/frontend select Noninteractive' | debconf-set-selections
RUN apt-get update
RUN apt-get install -y python-pip python-dev python-lxml libxml2-dev libxslt1-dev libxslt-dev libpq-dev zlib1g-dev && apt-get build-dep -y python-lxml && apt-get clean
# Specify your own RUN commands here (e.g. RUN apt-get install -y nano)

ADD requirements.txt requirements.txt
RUN pip install -r requirements.txt

WORKDIR /project

EXPOSE 80

Вы можете указать ваши собственные команды RUN, например, установить другие необходимые инструменты.

Затем вам нужно создать образ из этого, используя команду:

docker build -t username/image

Тут username — ваше имя пользователя Dockerhub и image — название вашего нового образа для данного проекта.

Когда вам успешно удалось создать основной образ, лучше будет вам загрузить в облако DockerHub. Это можно сделать командой docker push username/image. И не переживайте, в этом образе нет никакой информации из вашего проекта (кроме файла requiremets.txt). Если вы используете приватное хранилище DockerHub, удостоверьтесь в исполнении docker login перед загрузкой/выгрузкой образов.

Организация работы контейнеров

Итак, на данном этапе мы можем запустить наш контейнер с приложениям Django, но нам также нужно запустить некоторые другие контейнеры c Redis, базой данных PostgreSQL и Celery Worker. Чтобы упростить этот процесс, мы воспользуемся технологией Docker Compose, которая позволяет нам создать простой файл YML с инструкциями о том, какие контейнеры запускать и как линковать их между собой.

Давайте создадим этот волшебный файл и назовем его по умолчанию docker-compose.yml:

django:
  image: username/image:latest
  command: python manage.py supervisor
  environment:
    RUN_ENV: "$RUN_ENV"
  ports:
   - "80:8001"
  volumes:
   - .:/project
  links:
   - redis
   - postgres

celery_worker:
  image: username/image:latest
  command: python manage.py celery worker -l info
  links:
   - postgres
   - redis

postgres:
  image: postgres:9.1
  volumes:
    - local_postgres:/var/lib/postgresql/data
  ports:
   - "5432:5432"
  environment:
    POSTGRES_PASSWORD: "$POSTGRES_PASSWORD"
    POSTGRES_USER: "$POSTGRES_USER"  

redis:
  image: redis:latest
  command: redis-server --appendonly yes

Как вы видите, мы запустим четыре проекта под названиями django, celery_worker, postgres и redis. Эти названия важны для нас.

Итак, во-первых, наш файл загрузит образ Redis из dockerhub и запустит из него контейнер. Во-вторых, он загрузит образ Postgres и запустит контейнер с закрепленными данными из радела local_postgres. О создании локальной базы данных расскажу подробней в следющей статье.

Затем, этот файл запустит контейнер с нашим приложением Django, направит 8001-й порт изнутри на 80-й снаружи, свяжет ваш текущий каталог прямо с папкой /project внутри контейнера, пролинкует его с контейнерами Redis и Postgres и запустит супервайзера. И последнее, но не менее важное — наш контейнер с celery worker, который также пролинкован с Postgres и Redis.

Вы можете направить любое количество портов, если необходимо — для этого просто добавьте новые записи в соответствующий раздел. Также вы можете пролинковать любое количество контейнеров, например, контейнер с вашей базой данных. Используйте тег :latest для автоматической проверки обновленного образа на DockerHub.

Если вы назвали проект Redis Server именем redis в файле YML — вам нужно указать redis вместо localhost в вашем settings.py чтобы позволить вашему приложению Django подсоединиться к redis:

REDIS_HOST = "redis"
BROKER_URL = "redis"

То же с базой данных Postgres — используйте postgres вместо localhost:

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.postgresql_psycopg2',
        'NAME': 'database_name',
        'USER': os.getenv('DATABASE_USER', ''),
        'PASSWORD': os.getenv('DATABASE_PASSWORD', ''),
        'HOST': 'postgres',
        'PORT': '5432',
    }
}

Скрипт для повторного развертывания

Давайте создадим скрипт для повторного развертывания в один клик (давайте назовем его redeploy.sh):

#!/bin/sh
if [ -z "$RUN_ENV" ]; then
    echo 'Please set up RUN_ENV variable'
    exit 1
fi

if [ "$RUN_ENV" = "PROD" ]; then
    git pull
fi

docker-compose stop
docker-compose rm -f
docker-compose up -d

Давайте проверим, что он делает:
— Он проверяет, установлена ли переменная RUN_ENV и совершает выход, если нет
Если RUN_ENV установлена на PROD, он сделает команду ’git pull’ чтобы получить новую версию вашего проекта;
— Он остановит все проекты, указанные в файле docker-compose.yml;
— Он удалит все существующие контейнеры;
— Он запустит новые контейнеры.

Итак, это выглядит достаточно просто: чтобы сделать повторное развертывание, вам нужно всего лишь запустить ./redeploy.sh. Не забудьте дать права на исполнение (chmod +x redeploy.sh) этому скрипту и всем другим скриптам, описанным в инструкции.

Чтобы сделать быстрое повторное развертывание, используйте эту команду:

docker-compose up --no-deps -d django

Развертывание внутри контейнера

Нам нужно всего лишь запустить супервайзера для того, чтобы собственно запустить наш сервис внутри контейнера: python manage.py supervisor. Если вы не используете супервайзер, вы можете просто запустить ваш сервер вместо супервайзера.

Если же вы пользуетесь супервайзером, давайте посмотрим на файл ’supervisord.conf’:

[supervisord]
environment=C_FORCE_ROOT="1"

[program:__defaults__]
redirect_stderr=true
startsecs=10
autorestart=true

[program:gunicorn_server]
command=gunicorn -w 4 -b 0.0.0.0:8001 YourApp.wsgi:application
directory={{ PROJECT_DIR }}
stdout_logfile={{ PROJECT_DIR }}/gunicorn.log

Итак, супервайзер запустит ваш сервер Gunicorn с 4 работающими процессами и связанным портом 8001.

Процесс разработки

Чтобы зайти на ваш локальный сервер, пройдите по 127.0.0.1:8000.

Для повторного развертывания локальных изменений сделайте:
sh redeploy.sh

Чтобы просмотреть все логи в вашем проекте, выполните:
docker-compose logs

Чтобы быстро реализовать повторное развертывание изменений, используйте:
docker-compose restart django

Чтобы подсоединиться к Django — просто подсоединяйтесь (если ваш работающий контейнер назван CONTAINER):
docker exec -it CONTAINER python manage.py shell

Чтобы создать изначального суперпользователя:
from django.contrib.auth.models import User; User.objects.create_superuser(’admin’, Данный адрес e-mail защищен от спам-ботов, Вам необходимо включить Javascript для его просмотра. ’, ’admin’)

Выполнить миграцию:
docker exec -it CONTAINER python manage.py schemamigration blabla —auto

Или вы можете подсоединиться к bash внутри контейнера:
docker exec -it CONTAINER /bin/bash

Вы можете сохранить вашу локальную базу данных в файл .json (вы можете указать таблицу для сохранения):
docker exec -it CONTAINER python manage.py dumpdata > testdb.json

Или вы можете загрузить данные в вашу базу данных из файла:
docker exec -it CONTAINER python manage.py loaddata testdb.json

Используйте эту команду для мониторинга статуса ваших работающих контейнеров:
docker stats $(docker ps -q)

Используйте эту команду для удаления всех остановленных контейнеров:
docker rm -v `docker ps -a -q -f status=exited`

Вы можете играться с вашими контейнерами как вам захочется. Здесь — полезный Docker cheat sheet.

GIT

Когда сервер работает, он будет создавать дополнительные файлы, такие как .log, .pid и так далее. Вам не нужно их включать в репозиторий. Не забудьте создать файл .gitignore:

.idea
db.sqlide3
*.pyc
*.ini
*.log
*.pid
/static
.vagrant/

Статические файлы

У вас могут обнаружиться некоторые проблемы в работе со статическими файлами на рабочем сервере при использовании только gunicorn, поэтому не забудьте создать пустую папку /static/ в вашем корневом каталоге проекта с файлом __init__.py для обслуживания из него статики.

По этой схеме ваш файл settings.py должен включать:

STATIC_URL = '/static/'
STATIC_ROOT = os.path.join(BASE_DIR, 'static')

И для обслуживания статики с помощью gunicorn на рабочем сервере добавьте это в конец файла urls.py:

SERVER_ENVIRONMENT = os.getenv('RUN_ENV', '')
if SERVER_ENVIRONMENT == 'PROD':
    urlpatterns += patterns('', (r'^static/(?P<path>.*)$', 'django.views.static.serve', {'document_root': settings.STATIC_ROOT}), )

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


P.S. В следующей статье я расскажу, как запустить Docker практически где угодно при помощи Vagrant. Также рассмотрим, как создавать среду разработки на виртуальной машине, которую вы можете легко передать вашим сотрудникам, не беспокоясь о том, какие операционные системы установлены у них локально.

Похожие статьи:
В рубрике DOU Проектор все желающие могут презентовать свой продукт (как стартап, так и ламповый pet-проект). Если вам есть о чем...
Привет! Этот дайджест мы решили посвятить Ruby/Rails Gems, собрав гемы для решения типичных задач: от тестирования до безопасности...
Планируешь выучить JavaScript? Хватит думать — действуй! СКИДКА НА ОБУЧЕНИЕ 40% Brain Academy — лидер в отрасли комплексного...
C начала карантина Wix перешел на удаленку. Вряд ли кого-то сейчас этим удивишь, правда? Что может показаться...
Константин Мирин — CEO IT-компании Postindustria. Шесть лет назад он переехал в город Бар в Черногории и открыл...
Яндекс.Метрика