falbar Разработка вместе с Docker – руководство по использованию Flask и Postgres

Разработка вместе с Docker – руководство по использованию Flask и Postgres

19 января 2019 Перевод Туториал 1429 0

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

Реклама

Почему Docker?

Я являюсь частью управляемой студентами некоммерческой организации под названием Hack4Impact в Иллинойском университете в Урбане-Шампейне. Мы разрабатываем технические проекты для некоммерческих организаций, чтобы помочь им в выполнении их задач. Каждый семестр в нашем распоряжении находится несколько проектных команд из 5-7 студентов-разработчиков ПО разного уровня квалификации. Есть даже студенты, которые только закончили первый курс информатики в колледже.

Поскольку многие некоммерческие организации часто запрашивали веб-приложения, я курировал Flask Boilerplate, чтобы позволить командам быстро запускать их серверные службы REST API. Предоставлялись общие функции утилит, структура приложения, оболочки базы данных и соединения, а также документация по настройке, лучшие практики кодирования и действия по развертыванию Heroku.

Проблемы со средой разработки и зависимостями

Так как мы каждый семестр принимаем на работу новых студентов-разработчиков ПО, команды тратят много времени на настройку и устранение проблем среды. У нас часто было несколько участников, работающих в разных операционных системах. Мы сталкивались с множеством проблем (Windows, я указываю на вас). Хотя многие из этих проблем были тривиальными (например, запуск правильной версии БД PostgreSQL с правильным именем пользователя/паролем), это трата времени, которое могло быть вложено в сам продукт.

Кроме того, я написал документацию только для пользователей Mac OS с инструкциями по bash (у меня есть Mac). По сути, я оставил пользователей Windows и Linux без внимания. Я мог бы запустить несколько виртуальных машин и снова задокументировать настройки для каждой ОС, но зачем мне это делать, если есть Docker?

Пора воспользоваться Docker

С помощью Docker все приложение можно изолировать в контейнерах, которые можно переносить с компьютера на компьютер. Благодаря этому не возникает конфликтов между средами и зависимостями. Таким образом, вы можете «собрать один раз, запустить где угодно». Разработчики теперь могут установить только одну вещь – Docker – и ввести пару команд для запуска приложения. Новички смогут быстро начать развиваться, не беспокоясь о среде. В будущем некоммерческие организации также смогут быстро вносить изменения.

Docker также имеет много других преимуществ, таких как портативность и ресурсоэффективность (по сравнению с виртуальными машинами). Важно и то, что вы можете безболезненно настроить непрерывную интеграцию и быстро развернуть свое приложение.

Краткое описание основных компонентов Docker

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

Образы Docker

Образы Docker – это шаблоны, доступные только для чтения. Они описывают контейнер Docker. Они включают в себя конкретные инструкции, написанные в Dockerfile. Он определяет приложение и его зависимости. Можно представить, что это снимок вашего приложения в определенное время. Образы можно получить при введении команды docker build.

Контейнеры Docker

Контейнеры Docker являются экземплярами образов Docker. Они включают в себя ОС, код приложения, среду, системные инструменты, библиотеки и т. д. Можно объединить несколько Docker-контейнеров. Например, приложение Node.js может находиться в контейнере, который связан с контейнером БД Redis. Чтобы запустить контейнер Docker, нужно воспользоваться командой docker start.

Реестры Docker

Реестр Docker – это место, где можно хранить и распространять образы. Мы будем использовать базовые образы из DockerHub - бесплатного реестра, размещенного самим Docker.

Docker Compose

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

Пример Docker с Flask и Postgres

Держа в уме все компоненты, давайте приступим к настройке среды разработки Docker с использованием приложения Flask и Postgres в качестве хранилища данных. В оставшейся части этого поста я буду ссылаться на Flask Boilerplate – репозиторий, который я упоминал ранее, говоря о Hack4Impact.

В этой конфигурации мы создадим в Docker 2 образа:

  • App – приложение Flask, которое находится на порте 5000;
  • Postgres – база данных Postgres, которая находится на порте 5432.

Что касается верхнего каталога, эту конфигурацию определяет 3 файла:

  • Dockerfile – скрипт, составленный из инструкций по настройке контейнеров приложения. Каждая команда является автоматической и выполняется последовательно. Этот файл будет находиться в каталоге, в котором вы запускаете приложение (например, python manage.py runserver, python app.py или npm start). В нашем случае он находится в верхнем каталоге (где расположен manage.py). Dockerfile принимает инструкции Docker;
  • .dockerignore указывает, какие файлы не включать в контейнер. Работает так же, как и .gitignore, но в контейнерах Docker. Этот файл связан с Dockerfile;
  • docker-compose.yml – файл конфигурации для Docker Compose. Он позволит нам одновременно создавать образы app и postgres, определять объемы и состояния, в которых приложение зависит от postgres, и устанавливать необходимые переменные среды.
На 2 образа приходится один Dockerfile, потому что мы будем брать официальный образ Docker Postgres из DockerHub! Вы можете добавить свой собственный образ Postgres, написав для него отдельный Dockerfile, но в этом нет никакого смысла.

Dockerfile

Проясним еще раз: этот Dockerfile предназначен для контейнера приложения. В качестве обзора приведен весь Dockerfile – он получает базовый образ, копирует приложение, устанавливает зависимости и устанавливает конкретную переменную среды.

FROM python:3.6
LABEL maintainer "Timothy Ko <tk2@illinois.edu>"
RUN apt-get update
RUN mkdir /app
WORKDIR /app
COPY . /app
RUN pip install --no-cache-dir -r requirements.txt
ENV FLASK_ENV="docker"
EXPOSE 5000

Поскольку это приложение Flask использует Python 3.6, мы хотим, чтобы среда поддерживала его, так что он должен быть установлен в ней. К счастью, DockerHub имеет официальный образ, установленный поверх Ubuntu. В одной строке у нас будет базовый образ Ubuntu с Python 3.6, virtualenv и pip. В DockerHub есть множество образов. Если вы хотите начать с нового образа Ubuntu и «строить» поверх него, вы можете сделать это.

FROM python:3.6

Затем я отмечаю, что я являюсь специалистом по обслуживанию.

LABEL maintainer "Timothy Ko <tk2@illinois.edu>"

Теперь пришло время добавить приложение Flask к образу. Для простоты я решил скопировать приложение в каталог /app в нашем образе Docker.

RUN mkdir /app
COPY . /app
WORKDIR /app

WORKDIR – это, по сути, cd. COPY копирует определенный каталог в предоставленный каталог в образе. ADD - это еще одна команда, которая делает то же самое, что и COPY, но она также позволяет вам добавлять каталоги из URL. Таким образом, если вы хотите клонировать свой git-репозиторий, а не копировать его из локального каталога (для промежуточных и производственных целей), вы можете использовать такой путь. Однако обычно COPY следует использовать, если у вас нет URL. Каждый раз, когда вы используете RUN, COPY, FROM или CMD, вы создаете новый слой в образе Docker, который влияет на способ хранения и кэширования образов в Docker.

Раз уж мы скопировали каталог в образ, установим все наши зависимости, которые определены в файле requirements.txt.

Но, скажем, у вас было приложение Node, а не Flask. Тогда вы бы писали RUN npm install. Следующим шагом является указание Flask использовать конфигурации Docker, которые я жестко запрограммировал в config.py. В этой конфигурации Flask подключится к правильной базе данных, которую мы настроим позже. Поскольку у меня были производственные и обычные конфигурации разработки, я сделал так, чтобы Flask выбирал конфигурацию Docker всякий раз, когда для переменной среды FLASK_ENV задано значение docker. Итак, нам нужно установить это в образе нашего приложения.

ENV FLASK_ENV="docker"

Затем откроем порт 5000, на котором работает приложение Flask:

EXPOSE 5000

Вот и все! На какой бы ОС вы ни работали, как бы плохо вы ни выполняли инструкции, ваш образ Docker будет таким же, как и у других членов команды. Все благодаря этому Dockerfile.

Каждый раз, когда вы будете создавать образ, будут проводиться упомянутые команды. Вы можете построить образ с помощью sudo docker build -t app. Однако, если запускать контейнер с помощью sudo docker run app, в приложении возникнет ошибка по подключению к БД. Все потому, что мы еще не ввели в действие саму базу данных.

docker-compose.yml

Docker Compose позволяет одновременно делать так и одновременно создавать образ app. Весь файл выглядит так:

version: '2.1'
services:
  postgres:
    restart: always
    image: postgres:10
    environment:
      - POSTGRES_USER=${POSTGRES_USER}
      - POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
      - POSTGRES_DB=${POSTGRES_DB}
    volumes:
      - ./postgres-data/postgres:/var/lib/postgresql/data
    ports:
      - "5432:5432"
  app:
    restart: always
    build: .
    ports:
      - 5000:5000
    volumes:
      - .:/app

Для этого каталога я решил использовать версию 2.1, так как мне было удобнее с ней, да и на нее больше туториалов. Да, это моя единственная причина не использовать версию 3. В версии 2 вы должны предоставить «сервисы» или изображения, которые вы хотите включить. В нашем случае это app и postgres (это просто имена, на которые вы можете ссылаться, когда используете команды docker-compose. Их можно называть БД, api или чем-то еще).

Образ Postgres

Что касается Postgres Service, я указываю, что это образ postgres:10, которое является еще одним образом из DockerHub. Этот образ является образом Ubuntu, на котором установлен Postgres, и он автоматически запускает сервер Postgres.

postgres:
  restart: always
  image: postgres:10
  environment:
    - POSTGRES_USER=${USER}
    - POSTGRES_PASSWORD=${PASSWORD}
    - POSTGRES_DB=${DB}
  volumes:
    - ./postgres-data/postgres:/var/lib/postgresql/data
  ports:
    - "5432:5432"

Если вы хотите применить другую версию, просто замените «10» на что-то другое. Чтобы указать, какого пользователя, пароль и базу данных вы хотите использовать в Postgres, нужно заранее определить переменные среды. Это реализовано в официальном Dockerfile образа для Postgres. В этом случае образ postgres внедрит переменные среды $USER, $PASSWORD и $DB и сделает их переменными среды - POSTGRES_USER, POSTGRES_PASSWORD и POSTGRES_DB внутри контейнера postgres. Обратите внимание, что $USER и другие введенные переменные являются переменными среды, указанными на вашем собственном компьютере (точнее – это процесс командной строки, который вы используете для запуска команды docker-compose up). Введение учетных данных позволяет вам не передавать их в публичный каталог.

Docker Compose также автоматически внедрит переменные среды, если в том же каталоге, где есть файл docker-compose.yml, находится файл .env. Вот пример файла .env для такого случая:

USER=testusr
PASSWORD=password
DB=testdb

Таким образом, наша БД PostgreSQL будет называться testdb с пользователем testusr и с паролем password.

Наше приложение Flask будет подключаться к этой конкретной БД, потому что я записал его URL в конфигурации Docker, о чем я упоминал ранее.

Каждый раз, когда работа контейнера останавливается, а он сам удаляется, то также будут удаляться и данные. Значит, вы должны обеспечить постоянное хранилище данных, чтобы данные не удалялись. Есть 2 способа это сделать:

  • Docker Volumes;
  • Локальные каталоги.

Я решил подключить приложение локально к ./postgres-data/postgres, но местоположение может быть любым. Синтаксис всегда такой: [ХОСТ]:[КОНТЕЙНЕР]. Это означает, что любые данные из /var/lib/postgresql/data на самом деле хранятся в ./postgres-data.

volumes:
- ./postgres-data/postgres:/var/lib/postgresql/data

Тот же синтаксис используется и для портов.

ports:
- "5432:5432"

Образ app

Теперь определим образ app.

app:
  restart: always
  build: .
  ports:
    - 5000:5000
  volumes: 
    - .:/app
  depends_on:
    - postgres
  entrypoint: ["python", "manage.py","runserver"]

Сначала сделаем так, чтобы в нем был restart: always. Это значит, что в случае ошибки приложение будет перезапускаться. Эта функция особенно удобна при создании и запуске контейнеров. Обычно app запускается перед postgres. Значит, app попытается подключиться к БД и выдаст ошибку, ведь postgres еще не запущен. Без использованного нами свойства app бы просто перестал работать, и на этом все бы кончилось.

Затем мы определяем, что этот билд должен быть Dockerfile в текущем каталоге.

build: .

Следующий шаг важен для того, чтобы сервер Flask перезапускался при изменении кода в локальном каталоге. Это очень полезно, ведь вам не придется каждый раз выпускать новые версии образа, чтобы увидеть изменения. Чтобы добиться этого, делаем то же, что и ранее с postgres. Устанавливаем, что в каталоге /app в контейнере будет находиться все, что есть в . (в текущем каталоге). Благодаря этому все изменения в локальном репозитории будут перенесены в контейнер.

volumes:
  - .:/app

Затем сообщаем Docker Compose, что приложение зависит от контейнера postgres. Обратите внимание, что при изменении названия образа на что-то вроде database нужно заменить на него postgres.

depends_on:
  - postgres

Наконец, предоставим команду, которая вызывается для запуска приложения. В нашем случае это python manage.py runserver.

entrypoint: ["python", "manage.py","runserver"]

В случае Flask нужно точно сообщить, какой хост (порт) используется для приложения. Также определите, нужно ли, чтобы оно было в отладочном режиме при запуске. В manage.py я делаю это так:

def runserver():
    app.run(debug=True, host=’0.0.0.0', port=5000)

Теперь нужно задать и запустить приложение Flask и БД Postgress с помощью командной строки:

docker-compose build
docker-compose up -d
docker-compose exec app python manage.py recreate_db

По сути, последняя команда создает схему БД, которая определяется приложением Flask в Postgres.

Вот и все! Ваше приложение Flask теперь должно запускаться по ссылке http://localhost:5000!

Команды Docker

По началу может быть сложно запоминать и находить команды для Docker, так что по ссылке можно найти их список.

Заключение

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

Можно было бы добавить еще пару строчек, и тогда бы получилась полная версия приложения с Nginx и Gunicorn. Если бы я захотел использовать Redis для кэширования сессии или для создания очереди, я бы мог сделать это очень быстро. У всей моей команды была бы та же среда разработки, что и у меня, если бы они использовали эти образы Docker.

Кстати, при желании я бы мог даже моментально сделать 20 версий приложения Flask. Спасибо за чтение!

Реклама
Комментариев еще не оставлено
no_avatar