Docker Compose или мамкин distributed guy

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:

Тудушечки

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

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

Docker+Consul+Nginx, используем Service Discovery для конфигурирования Reverse Proxy

06 April 2016

Зачем это вообще нужно?

Изведав Service Discovery в действии на примере Solr Cloud, я возжелал использовать этот подход как можно шире.
Что может быть лучше, чем возможность просто добавить еще один сервер, а он уже сам заберёт настройки из хранилища,
узнает об остальных запущенных серверах и т.д.

Solr использует для Service Discovery Zookeeper, что и понятно: Zookeeper, как и Solr являются проектами инкубатора
Apache. Но, если уж начистоту, то Zookeeper достаточно сложное для использования хранилище: Service Discovery, например,
для него приходится реализовывать самостоятельно. Существует специальный проект Curator, который реализует основные
шаблоны работы с Zookeeper.

Поисследовав немного существующие проекты, я пришел к выводу что самым удачным для моих задач является http://consul.io.
Наиболее интересным фишками Consul является:

Как мы будем использовать Consul?

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

Для того чтобы сгенерировать конфигурационный файл, нам нужен какой-нибудь инструмент, который бы смог получать нужные данные из консула. Вообще, так как нам доступен Rest API, мы бы могли грузить информацию о зарегистрированных сервисах с помощью простого python-скрипта. Но лучше не городить велосипеды, а воспользоваться решением от авторов Consul: consul-template.

consul-template - аналог confd, но нативно поддерживающий Service Discovery Consul. Позволяет как сгенерировать конфиграционный файл однажды, так и следить за изменениям в выбранном сервисе и автоматически перегенерировать файл в случае изменений.

Поднимаем всё в Docker.

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

  1. Consul
    Для Consul мы будем использовать готовый образ: gliderlabs/consul-server.
    Запустим его с помощью команды

    docker -d -p 8500:8500 --net consul gliderlabs/consul-server --bootstrap
    

  2. Зарегистрируем наш сервис с помощью REST API

    curl -X PUT 'http://localhost:8500/v1/agent/service/register --data '{ "ID": "jenkins", "Name": "web", "Address": "jenkins", "Port": 8080
    
  3. Создадим образ с использованием nginx и consul-template

    • Dockerfile:

      FROM debian:jessie
      MAINTAINER sala
      
      # Устанавливаем  nginx, curl и unzip
      RUN apt-get update && apt-get install curl unzip nginx -y
      # Скачиваем consul-template, распаковываем, готовим папку для шаблончиков
      RUN curl -L https://releases.hashicorp.com/consul-template/0.14.0/consul-template_0.14.0_linux_amd64.zip -o consul-template.zip && \
       unzip consul-template.zip -d /usr/local/bin && \
       cd /usr/local/bin && \
       chmod +x consul-template && \
       mkdir -p /etc/consul-template/templates
      # Публикуем стандартные порты для http и https
      EXPOSE 80
      EXPOSE 443
      # Добавляем шаблон, конфигурационный файл nginx и скрипт запуска
      ADD templates/ /etc/consul-template/templates
      ADD nginx.conf /etc/nginx/nginx.conf
      ADD scripts/start.sh .
      CMD ./start.sh
      
    • templates/nginx.ctmpl

      {{range service "web"}}
      server {
          server_name {{.ID}}.shadam.ru;
          location / {
              proxy_pass http://{{.Address}}:{{.Port}};
              proxy_connect_timeout 90;
              proxy_send_timeout 90;
              proxy_read_timeout 90;
      
              proxy_buffers                   8 64k;
              proxy_buffer_size               64k;
              proxy_busy_buffers_size         64k;
              proxy_temp_file_write_size      10m;
      
              proxy_set_header        Host            $http_host;
              #proxy_set_header       Host            $host;
              proxy_set_header        Referer         $http_referer;
              proxy_set_header        User-Agent      $http_user_agent;proxy_redirect off;
              proxy_set_header        X-Real-IP       $remote_addr;
              proxy_set_header        X-Forwarded-For $proxy_add_x_forwarded_for;
              proxy_set_header        X-Forwarded-Proto       $scheme;
      
              client_max_body_size    64m;
              client_body_buffer_size 1m;
          }
      }
      
      {{end}}
      
    • scripts/start.sh

      #!/bin/bash
      
      set -eo pipefail
      
      export CONSUL_PORT=${CONSUL_PORT:-8500}
      export HOST_IP=${HOST_IP:-consul}
      export CONSUL=$HOST_IP:$CONSUL_PORT
      
      echo "[nginx] booting container. CONSUL: $CONSUL."
      
      # Try to make initial configuration every 5 seconds until successful
      consul-template -once -retry 5s -consul $CONSUL -template "/etc/consul-template/templates/nginx.ctmpl:/etc/nginx/conf.d/consul-template.conf"
      
      # Put a continual polling `confd` process into the background to watch
      # for changes every 10 seconds
      consul-template  -consul $CONSUL -template "/etc/consul-template/templates/nginx.ctmpl:/etc/nginx/conf.d/consul-template.conf:service nginx reload" &
      echo "[nginx] consul-template is now monitoring consul for changes..."
      
      # Start the Nginx service using the generated config
      echo "[nginx] starting nginx ..."
      nginx
      

Теперь мы можем построить наш образ с помощью команды

    docker build -t saladinkzn/nginx-consul-template .

После того как мы построили образ, мы можем его запустить.

    docker run -d -p 80:80 --net consul saladinkzn/nginx-consul-template

Ура! Теперь у нас есть работающий Nginx, который автоматические создает дополнительные записи в своем конфиге для каждого добавленного сервиса.

Что нам это дает? Возможность 1 раз сконфигурировать данную подсистему и далее легко устанавливать дополнительные сервисы, автоматически получая сконфигурированный Nginx.

Шпаргалка Docker

22 October 2014

Создание образа

Управление образами

Запуск и остановка контейнеров

Управление контейнерами

Синтаксис Dockerfile

Dockerfile служит скриптом сборки для команды docker build. Перед началом сборки docker передает сборщику всё содержимое папки с Dockerfile'ом,
поэтому располагать его в корневой директории системы будет не лучшей идеей.

Формат файла:

# Комментарий
ИНСТРУКЦИЯ аргументы

Первая инструкция обязательно должна быть инструкцией FROM.

Инструкции:

-----------------------------
Образ - image
Контейнер - container

Шпаргалка GIT

14 October 2014

git init
Создание репозитория в текущей папке
git clone ПУТЬ

Копирует существующий репозиторий, находящийся по указанному пути

Пример.

git clone git@github.com:saladinkzn/quantum.git
git status
Выводит список измененных файлов
git diff
Отображает разницу между двумя версиями файлов
git diff ФИКСАЦИЯ ФАЙЛ
Отображает разницу в между рабочей копией и фиксацией в файле
Значения по умолчанию: ФИКСАЦИЯ - HEAD, ФАЙЛ - .
git diff ФИКСАЦИЯ1..ФИКСАЦИЯ2 ФАЙЛ
Отображает разницу между ФИКСАЦИЕЙ1 и ФИКСАЦИЕЙ2 Значений по умолчанию: ФИКСАЦИЯ1 - HEAD, ФИКСАЦИЯ2 - HEAD, ФАЙЛ - .
git diff ФИКСАЦИЯ1...ФИКСАЦИЯ2 ФАЙЛ
Отображает изменения, произошедшие в ФИКСКАЦИИ2, начиная с общего предка ФИКСАЦИИ1 и ФИКСАЦИИ2 Значения по умолчанию: ФИКСАЦИЯ1 - HEAD, ФИКСАЦИЯ2 - HEAD, ФАЙЛ - .
git log
Отображает список фиксаций в текующей ветке
ВЕТКА
Отображает список фиксаций в ВЕТКЕ
ВЕТКА1..ВЕТКА2
Отображает список фиксаций в ВЕТКЕ2, которых нет в ВЕТКЕ1
--graph
Меняет отображение на отображение в виде графа
--oneline
Меняет отображение на отображение в однострочном виде, удобно для дальнейшей машинной обработки
a123123 Some changes
ade2132 Init commit
-КОЛИЧЕСТВО
Отображает количество отображаемых фиксаций
git commit -m "сообщение"
Фиксирует добавленные изменения в текущей ветке
--amend
Не создаёт новую фиксацию, а дополняет предыдущую
git checkout НАЗВАНИЕ_ВЕТКИ
Переключается в существующую ветку
-b
Создает новую ветку
-- ФАЙЛ
Переключает определенные файлы, а не всю рабочую копию
git merge НАЗВАНИЕ_ВЕТКИ
Осуществляет слияние веток: изменения из указанной ветки попадают в текущую
git mergetool
Вызывает внешний инструмент для разрешения конфликтов слияния
git merge --abort
Отменяет текущее слияние
git rebase
git rebase АПСТРИМ ВЕТКА
Сохраняет фиксации, которых нет в АПСТРИМЕ во временную область, сбрасывает текущее состояние к АПСТРИМУ, а затем применяет фиксации поверх АПСТРИМА.
git push КУДА НАЗВАНИЕ_ВЕТКИ
Проталкивает изменения из локального репозитория во внешний
git fetch ОТКУДА
Забирает изменения из внешнего репозитория
Если параметр не указан — вместо ОТКУДА подставляется origin (т.е. репозиторий, из которого был выполнен clone)
git pull ОТКУДА
Забирает изменения из внешнего репозитория и пытается автоматичски выполнить слияние
--rebase
Выполняет git rebase вместо git merge после fetch'а


Older posts are available in the archive.