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:
Неплохо было бы создать отдельный режим для запуска приложения в режиме для разработки (с запущенным дебаг режимом и проброшенным портом для удаленной отладки).
06 April 2016
Изведав Service Discovery в действии на примере Solr Cloud, я возжелал использовать этот подход как можно шире.
Что может быть лучше, чем возможность просто добавить еще один сервер, а он уже сам заберёт настройки из хранилища,
узнает об остальных запущенных серверах и т.д.
Solr использует для Service Discovery Zookeeper, что и понятно: Zookeeper, как и Solr являются проектами инкубатора
Apache. Но, если уж начистоту, то Zookeeper достаточно сложное для использования хранилище: Service Discovery, например,
для него приходится реализовывать самостоятельно. Существует специальный проект Curator, который реализует основные
шаблоны работы с Zookeeper.
Поисследовав немного существующие проекты, я пришел к выводу что самым удачным для моих задач является http://consul.io.
Наиболее интересным фишками Consul является:
В данной статье мы будем использовать Consul для автоматической регистрации различных сервисов в реверс-прокси (Nginx). Nginx не поддерживает интеграцию с Consul из коробки, поэтому мы будем использовать подход с генерацией конфигурационного файла и вызова nginx reload.
Для того чтобы сгенерировать конфигурационный файл, нам нужен какой-нибудь инструмент, который бы смог получать нужные данные из консула. Вообще, так как нам доступен Rest API, мы бы могли грузить информацию о зарегистрированных сервисах с помощью простого python-скрипта. Но лучше не городить велосипеды, а воспользоваться решением от авторов Consul: consul-template.
consul-template - аналог confd, но нативно поддерживающий Service Discovery Consul. Позволяет как сгенерировать конфиграционный файл однажды, так и следить за изменениям в выбранном сервисе и автоматически перегенерировать файл в случае изменений.
Строить инфраструктуру мы будем на основе Docker. Это позволит нам не возиться с установкой каждого сервиса в отдельности, получить готовое решение, которое можно будет легко развернуть как на одном, так и на нескольких серверах.
Consul
Для Consul мы будем использовать готовый образ: gliderlabs/consul-server
.
Запустим его с помощью команды
docker -d -p 8500:8500 --net consul gliderlabs/consul-server --bootstrap
Зарегистрируем наш сервис с помощью REST API
curl -X PUT 'http://localhost:8500/v1/agent/service/register --data '{ "ID": "jenkins", "Name": "web", "Address": "jenkins", "Port": 8080
Создадим образ с использованием 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.
22 October 2014
docker pull ОБРАЗ
- загружает образ из Docker Hub (аналог GitHub для Docker)
docker build ПУТЬ | URL
- создает образ с помощью Dockerfile
Параметры:
-t
| --tag=""
- помечает созданный образ переданным названием (и, тэгом, если он будет передан)--rm
- Удаляет промежуточные контейнеры после успешной сборки (по умолчанию == true)docker rmi
- Удаляет образ, образ не может быть удален, если существуют контейнеры (даже незапущенные), которые основаны
на данном образе
Параметры:
-f
- позволяет удалить образ даже если на нём основаны контейнерыdocker images
- Отображает список всех существующих образов
Параметры:
-a
| --all
- отображает все образы (по умолчанию не отображает промежуточные контейнеры)-q
- отображает только id образов, вместо таблицыdocker run ОБРАЗ [КОМАНДА + АРГУМЕНТЫ]
- Запускает выбранный образ в новом контейнере
Параметры:
-d
| --detach
- запускает контейнер в фоновом режиме и выводит только id свежесозданного контейнера. (по умолчанию == false
)-i
| --interactive
- запускает контейнер в интерактивном режиме (оставляет STDIN открытым, даже если контейнер запущен в неприкрепленном режиме)-t
| --tty
- запускает псевдотерминал, часто используется с -i
-p
| --publish=[]
- пробрасывает порты контейнера в хост. Формат: ip:hostPort:containerPort | ip::containerPort | hostPort:containerPort | containerPort-e
| --env=[]
- пробрасывает переменные окружения внутрь контейнера.-v
| --volume=[]
- пробрасывает директорию файловой системы внутрь контейнераdocker stop КОНТЕЙНЕР
- останавливает контейнер, передавая внутрь SIGTERM, а по истечении таймаута - SIGKILL
docker start КОНТЕЙНЕР
- запускает остановленный контейнер.-i
| --interactive
- аналогично docker run -idocker restart КОНТЕЙНЕР
- Перезапускает выбранный контейнер с помощью docker stop и docker startdocker kill КОНТЕЙНЕР
- Убивает контейнер, передавая внутрь SIGKILLdocker port КОНТЕЙНЕР
- отображает маппинг портов между хостом и контейнеромdocker ps
- отображает список запущенных контейнеров-a
| --all=(true|false)
- отображать ли все контейнеры. По умолчанию == false
, т.е. отображаются только запущенные контейнеры-q
- отображает только ID контейнеров вместо таблицыdocker rm КОНТЕЙНЕР
- удаляет контейнер. По умолчанию можно удалить только запущенный контейнер.-f
| --force=(true|false)
- позволяет удалить запущенный контейнер. Используется передача SIGKILL внутрь.docker diff
- отображает изменения относительно образа.Dockerfile служит скриптом сборки для команды docker build
. Перед началом сборки docker передает сборщику всё содержимое папки с Dockerfile'ом,
поэтому располагать его в корневой директории системы будет не лучшей идеей.
Формат файла:
# Комментарий
ИНСТРУКЦИЯ аргументы
Первая инструкция обязательно должна быть инструкцией FROM.
Инструкции:
FROM ОБРАЗ
| FROM ОБРАЗ:ТЭГ
- Задает базовый образ для последующих инструкций. Может встречаться несколько раз в одном Dockerfile,
если необходимо собрать несколько образов за раз.
MAINTAINER имя
- Позволяет задать поле Author сгенерированного образа
RUN команда
| RUN ["исполняемый файл", "параметр1", "параметр2", ..]
- Запускает команду на основе текущего образа и фиксирует изменения в новом образе. Новый образ будет использован для исполнения последующих инструкций. Первый синтаксис подразумевает запуск команд в стандартной оболочке (bin\sh -c)
CMD ["исполняемый файл", "параметр1", "параметр2"]
| CMD ["параметр1", "параметр2"]
| CMD команда параметр1 параметр2
- Предоставляет значения по умолчанию для запуска контейнера. Эти значения могут как включать исполняемый файл (варианты 1, 3), так и не включать его (вариант 2). В последнем случае запускаемая команда
должна быть задана с помощью инструкции ENTRYPOINT
.
EXPOSE порт <порт...>
- Информирует Docker, что контейнер будет прослушивать указанные порты во время исполнения. Docker может использовать эту информацию, чтобы соединять контейнеры между собой используя связи. EXPOSE
сам по себе не делает порт доступным из хостовой системы. Для того, чтобы открыть порты в хостовую систему следует использовать флаг -p
.
ENV ключ значение
- Позволяет задавать переменные окружения. Эти переменные будут использованы при запуске контейнера из собранного образа. Могут быть просмотрены с помощью команды docker inspect
, а также переопределены с помощью флага --env
команды docker run
.
ADD ОТКУДА <ОТКУДА...> КУДА
- Используется для добавления новых файлов, директорий или ссылок на удалённые файлы в файловую систему контейнера. Несколько ОТКУДА может быть передано одновременно, но в этом случае все адреса должны быть относительны для директории, из которой происходит сборка. Каждый вхождение ОТКУДА может содержатьодин или несколько символов подстановки, которые будут разрешены с использование функции языка Go filepath.Match. КУДА должен быть абсолютным путем внутри контейнера.
ENTRYPOINT ["исполняемый файл", "параметр1", "параметр2"]
| ENTRYPOINT команда параметр1 параметр2
- позволяет сконфигурировать контейнер так, чтобы он запускался как исполняемый файл. В отличии от команды CMD
значение не будет переопределено аргументами, переданными в команду docker run
. Таким образом, аргументы из команды docker run
будут переданы внутрь контейнера, т.е. docker run ОБРАЗ -d
передаст -d исполняемому файлу.
VOLUME [ПУТЬ]
- создает точку монтирования с указанным именем и помечает её как содержащую подмонтированные разделы из хостовой системы или других контейнеров. Значение может быть задано как массив JSON, например, VOLUME ["/var/log/"]
, так и как обычной строкой с одним или несколькими аргументами, например VOLUME /var/log
или VOLUME /var/log /var/db
USER имя
- позволяет задавать имя пользователя или UID, который будет использован для запуска образа, а так же для любой из инструкций RUN
WORKDIR ПУТЬ
- задает рабочую директорию для команд RUN
, CMD
и ENTRYPOINT
. Инструкция может быть использована несколько раз. Если ПУТЬ относителен, то он будет относительным для ПУТИ, заданным предыдущей инструкцией WORKDIR
.
-----------------------------
Образ - image
Контейнер - container
14 October 2014
Копирует существующий репозиторий, находящийся по указанному пути
git clone git@github.com:saladinkzn/quantum.git
git diff ФИКСАЦИЯ ФАЙЛ
git diff ФИКСАЦИЯ1..ФИКСАЦИЯ2 ФАЙЛ
git diff ФИКСАЦИЯ1...ФИКСАЦИЯ2 ФАЙЛ
--graph
--oneline
a123123 Some changes ade2132 Init commit
-КОЛИЧЕСТВО
--amend
-b
-- ФАЙЛ
git rebase АПСТРИМ ВЕТКА
--rebase
git rebase
вместо git merge
после fetch'аOlder posts are available in the archive.