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

Почему Докер?

Я работаю в управляемой студентами некоммерческой организации под названием Hack4Impact в UIUC, где мы разрабатываем технические проекты для некоммерческих организаций, чтобы помочь им в выполнении их миссий. Каждый семестр у нас есть несколько проектных групп из 5–7 студентов-разработчиков программного обеспечения с различными уровнями навыков, включая студентов, которые только закончили свой первый курс информатики на уровне колледжа.

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

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

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

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

Войдите в 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 - это место для хранения и распространения образов Docker. Мы будем использовать образы Docker в качестве базовых образов из DockerHub, бесплатного реестра, размещенного на самом Docker.

Docker Compose

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

Пример Docker с Flask и Postgres

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

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

  • app - приложение Flask, обслуживаемое через порт 5000
  • postgres - база данных Postgres обслуживается через порт 5432

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

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

Примечание. Для двух образов существует только один файл Dockerfile, потому что мы будем брать официальный образ Docker Postgres с DockerHub! Вы можете включить свой собственный образ Postgres, написав для него свой собственный файл Docker, но в этом нет смысла.

Dockerfile

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

FROM python:3.6
LABEL maintainer "Timothy Ko <[email protected]>"
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 <[email protected]>"

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

RUN mkdir /app
COPY . /app
WORKDIR /app

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

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

RUN pip install --no-cache-dir -r requirements.txt

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

ENV FLASK_ENV="docker"

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

EXPOSE 5000

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

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

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. Вы называете их database и api или как угодно, что плавает ваша лодка).

Изображение Postgres

Глядя на службу Postgres, я указываю, что это образ 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 Docker. В этом случае изображение postgres вставит переменные среды $USER, $PASSWORD и $DB и сделает их POSTGRES_USER, POSTGRES_PASSWORD и POSTGRES_DB переменными среды внутри контейнера postgres. Обратите внимание, что $USER и другие введенные переменные среды - это переменные среды, указанные на вашем собственном компьютере (в частности, процесс командной строки, который вы используете для запуска команды docker-compose up. Введя свои учетные данные, это позволяет вам не фиксировать свои учетные данные в общедоступном репозитории. .

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

USER=testusr
PASSWORD=password
DB=testdb

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

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

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

  • Докер-тома
  • Монтирование локального каталога

Я решил установить его локально на ./postgres-data/postgres, но он может быть где угодно. Синтаксис always[HOST]:[CONTAINER]. Это означает, что любые данные из /var/lib/postgresql/data фактически хранятся в ./postgres-data.

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

Мы будем использовать тот же синтаксис для портов:

ports:
- "5432:5432"

изображение приложения

Затем мы определим изображение 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 container. Обратите внимание: если вы измените имя изображения на другое, например 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 и базу данных Postgres, используя командную строку:

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

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

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

Команды Docker

Запоминание и поиск команд Docker вначале может быть довольно неприятным, поэтому вот их список! Я также написал кучу часто используемых в моих Документах по Flask Boilerplate, если вы хотите сослаться на это.

Заключение

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

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

Мало того, я мог бы развернуть 20 экземпляров приложения Flask за секунды, если бы захотел. Спасибо за прочтение! :)

Если у вас есть какие-либо мысли и комментарии, не стесняйтесь оставлять комментарии ниже или напишите мне по адресу [email protected]! Кроме того, не стесняйтесь использовать мой код или поделиться им со своими коллегами!