13 April 2016

Зачем нужно?

Даже самый простой современный сервис состоит из нескольких приложений: как минимум, реверс-прокси, СУБД и самого веб приложения. В последнее время к ним часто стали добавлять Redis для сессий, Solr/Sphinx для поиска и многое другое. Да и само монолитное бизнес-приложение всё чаще разбивают на отдельные сервисы, микро и не очень. Но с большой силой приходит и большая ответственность: каждый новый сервис всё сложнее поддерживать, сложнее развернуть в тестовом окружении.

Решение

Для разворачивания приложений используем Docker - удобное средство для управления контейнерами. С помощью контейнеров больше не придется изучать длинные инструкции по установке СУБД или nginx, можно легко использовать утилиты, написанные на Ruby или Pyhton (и даже не придется использовать virtualenv).

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

Итак, пускай у нас есть простой Java-сервис. Сейчас я изучаю messaging, поэтому будем использовать Compose для запуска двух приложений: Publisher и Subscriber, а также сам брокер очередей RabbitMQ.

Схема нашего приложения:

gradle/
    - wrapper/
        - gradle-wrapper.jar
        - gradle-wrapper.properties
publisher/
    - src/main/java/...
    - build.gradle
    - Dockerfile
subscriber/
    - src/main/java/...
    - build.gradle
    - Dockerfile
build.gradle
docker-compose.yml
gradlew
gradlew.bat

Не буду подробно описывать структуру градлового билда. Там используется application plugin который генерирует tar-архив, со всеми необходимыми библиотеками, а также стартовые скрипты для запуска. Результаты работы этого плагина идеально подходят нам для создания образа нашего приложения.

Вот пример Dockerfile для просто java-приложения:

FROM saladinkzn/debian-java
RUN mkdir /workdir
ADD build/distributions/app1.tar /workdir/
WORKDIR /workdir
CMD /bin/bash app1/bin/app1
EXPOSE 8080

Абсолютно аналогично делаем Dockerfile для второго нашего приложения и переходим к самому главному - файлу настройки Docker Composer: docker-compose.yml.

version: '2'
services:
  # RabbitMQ, используем стандартный библиотечный образ, вытаскиваем наружу стандартный порт 5672, хотя это и не обязательно.
  rabbitmq:
    image: rabbitmq
    ports:
      - "5672:5672"
    networks:
      - inner
  # Наше приложение, строим его с использованием результатов Gradle билда.
  app1:
    # Имя образа, которое будет присвоено построенному образу
    image: app1
    # Контекст для docker build
    build: ./app1
    # Пробрасываем порты из контейнера наружу
    ports:
      - "8081:8080"
    # Присоединяемся к внутренней сети
    networks:
      - inner
    # Говорим Compose, что наш контейнер нужно запускать после образа с брокером очередей.
    depends_on:
      - rabbitmq
  app2:
    image: app2
    build: ./app2
    ports:
      - "8082:8080"
    networks:
      - inner
    depends_on:
      - rabbitmq
networks:
  inner:

Тудушечки

Неплохо было бы создать отдельный режим для запуска приложения в режиме для разработки (с запущенным дебаг режимом и проброшенным портом для удаленной отладки).

Использованная литература