Статьи

Учебник по многоуровневой архитектуре с использованием Docker

Фон

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

Предпосылки

В этом руководстве предполагается, что вы успешно установили Git и Docker. Я запускаю Boot2Docker на Mac, что позволяет запускать Docker почти так же, как если бы он был изначально установлен на Mac (на ПК Boot2Docker не так прост). Я предполагаю некоторый базовый уровень знакомства с Docker (хороший источник информации — https://docs.docker.com/articles/basics/ ).

сценарий

Сценарий, который я разработал для целей этого урока, изображен ниже:

дд

Как видите, он включает в себя несколько уровней:

MongoDB : это наш уровень персистентности, который будет заполнен демо-данными, представляющими клиентов.

Сервер API : Этот уровень представляет службы API Restful. Он предоставляет данные монго, упакованные в формате JSON (для целей данного руководства он действительно не использует бизнес-логику, которая обычно присутствует). Этот уровень разработан в Java с использованием Spring Web Services и Spring Data.

WebClient : это действительно простой уровень, который демонстрирует веб-приложение, написанное в среде Polymer UI Google. Вся бизнес-логика находится в Javascript, и туннель CORS используется для доступа к данным API из уровня сервисов.

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

Конфигурация MongoDB

Примечание. Код проекта для этого уровня находится по адресу: https://github.com/dajevu/docker-mongo .

Dockerhub содержит официальный образ для монго, который мы будем использовать в качестве отправной точки ( https://registry.hub.docker.com/u/dockerfile/mongodb/ ). В случае, если нам нужно опираться на то, что установлено в этом образе, я решил клонировать образ, просто скопировав его Dockerfile (изображения могут быть либо двоичными, либо декларативными через текстовый Dockerfile). Вот Dockerfile:

img1

Строка 5 идентифицирует базовый образ, используемый для установки mongo — это официальный образ dockerfile / ubuntu. Поскольку версия не указана, мы получаем последнюю версию.

Примечание . Комментарии в Dockerfiles начинаются с #.

Строка 8 начинает установку базы данных mongo вместе с некоторыми дополнительными инструментами, такими как s curl, git и т. Д. Ubuntu apt-get является стандартным инструментом управления пакетами Ubuntu.

Строки 16 и 18 устанавливают некоторые точки монтирования для того, где будут храниться файлы базы данных mongo (вместе с точкой монтирования на случай, если она понадобится нам позже). В строке 21 мы устанавливаем рабочий каталог в качестве этой точки подключения данных (из строки 16).

И наконец, мы идентифицируем некоторые порты, которые должны быть доступны в Mongo (строки 27 и 28).

Предполагая, что вы взяли код из моего репозитория git ( https://github.com/dajevu/docker-mongo ), вы можете запустить запуск этого образа Docker, запустив скрипт runDockerMongo.sh (в Windows Boot2Docker вам потребуется получить код внутри контейнера VM, на котором работает Docker). Этот скрипт просто содержит следующее:

1
docker run -t -d -p 27017:27017 --name mongodb jeffdavisco/mongodb mongod --rest --httpinterface --smallfiles

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

img2

Как видно из вышесказанного, в этом случае он запустил Mongo, используя идентификатор контейнера 4fb383781949 . Получив идентификатор контейнера, вы можете просмотреть журналы сервера контейнера, используя:

1
docker logs --tail="all" 4fb383781949 #container Id

Если вы не видите никаких результатов от docker ps, это означает, что контейнер запустился, но затем немедленно завершился. Попробуйте ввести команду docker ps -a- чтобы определить идентификатор контейнера отказавшей службы, и затем вы можете использовать docker logs чтобы определить, что пошло не так. Вам нужно будет удалить этот контейнер перед запуском нового с помощью docker rm .

После запуска контейнера Mongo мы можем заполнить его некоторыми демонстрационными данными, которые мы будем использовать для уровня обслуживания API. Каталог с именем northwind-mongo-master присутствует в файлах проекта и содержит скрипт mongo-import-remote.sh . Давайте посмотрим на содержимое этого файла:

img3

Этот скрипт просто запускает каждый файл .csv, присутствующий в каталоге, и использует утилиту mongoimport для загрузки коллекции. Тем не менее, есть одна маленькая морщинка. Поскольку mongoimport подключается к удаленной базе данных mongo, требуется IP-адрес удаленного хоста, и этот удаленный хост является только что запущенным контейнером mongo. Как я определил, какой IP-адрес использовать? Если вы используете Boot2Docker, вы можете просто запустить boot2docker ip — он предоставит вам IP-адрес используемого хоста Docker. Если вы работаете непосредственно на хосте, на котором работает Docker, вы можете подключиться через localhost в качестве вашего IP-адреса (поскольку этот порт был открыт для хоста Docker, когда мы запустили контейнер).

Чтобы загрузить тестовые данные, обновите этот скрипт, чтобы он имел правильный IP-адрес для вашей среды. Затем вы можете запустить скрипт ( mongo-import-remote.sh ), и он должен заполнить базу данных mongo примерами данных. Если вы используете MongoHub, вы можете подключиться к удаленному хосту mongo и увидеть следующие доступные коллекции:

img4

К счастью, нам не нужно преодолевать такие препятствия на оставшихся контейнерах.

Конфигурация уровня обслуживания API

Служба API — это очень простой веб-сервис на основе Restful, который просто предоставляет некоторые данные монго в формате JSON (при условии, что вы можете получить доступ к монго напрямую через его API отдыха, но в большинстве реальных сценариев вы никогда не будете разоблачить это публично). Веб-приложение, написанное на Java Spring, использует библиотеку Spring Data для доступа к удаленной базе данных mongo.

Файлы проекта для этого уровня расположены по адресу: https://github.com/dajevu/docker-maven-tomcat . Хотя я не буду описывать всю конфигурацию / настройку Spring (в конце концов, это не демонстрация Spring), давайте кратко рассмотрим основной класс сервиса, который будет иметь доступный веб-сервис:

img5

Используя аннотации Spring для веб-сервисов Spring, мы аннотируем этот контроллер в строке 5, чтобы Spring мог предоставлять последующие сервисы, используя базовый URL /customer . Затем мы определяем один метод, который используется для предоставления всех данных о клиентах из коллекции Mongo. Класс CustomerDAO содержит логику взаимодействия с Mongo.

Давайте посмотрим на Dockerfile, используемый для запуска этого веб-сервиса (используя Jetty в качестве веб-сервера):

img6

Как вы можете видеть, в строке 2 мы идентифицируем базовый образ для служб API как образ Java 7. Подобно Dockerfile mongo, следующее, что мы делаем в строках 8-15, это установка различных необходимых зависимостей, таких как git, maven и клиент mongo.

Затем, в отличие от нашего mongo Dockerfile, мы используем git для установки нашего веб-приложения на Java. Это делается в строках с 21 по 26. Мы создаем расположение для наших исходных файлов, а затем устанавливаем их с помощью git clone. Наконец, в строке 28 мы видим скрипт оболочки с именем run.sh Давайте посмотрим на содержимое этих скриптов:

1
2
3
#!/bin/bash
echo `env`
mvn jetty:run

Как видите, это довольно минимально. Сначала мы выводим переменные среды из этого контейнера, чтобы в целях устранения неполадок их можно было видеть при проверке журналов докера. Затем мы просто запускаем веб-приложение, используя mvn jetty:run . Поскольку исходный код приложения уже был загружен с помощью git clone , maven автоматически скомпилирует веб-приложение и затем запустит веб-сервер Jetty.

Теперь, возможно, вам интересно, как веб-сервис узнает, как подключиться к базе данных Mongo? Хотя мы открыли порт базы данных Mongo для узла докера, как определяется соединение в приложении java, чтобы оно указывало на правильное местоположение. Это делается с помощью переменных среды, автоматически создаваемых при указании зависимости / связи между двумя контейнерами. Чтобы понять, как это сделать, давайте посмотрим на скрипт запуска, используемый для запуска контейнера, runDockerMaven.sh :

1
2
3
docker run -d --name tomcat-maven -p 8080:8080
\ —link mongodb:mongodb jeffdavisco/tomcat-maven:latest
\ /local/git/docker-maven-tomcat/run.sh

Как видите, опция -link используется для уведомления докера о том, что у этого контейнера есть зависимость от другого контейнера, в данном случае — нашего экземпляра Mongo. Если присутствует, опция -link создаст набор переменных среды, которые заполняются при запуске контейнера. Давайте рассмотрим, как выглядят эти переменные среды, с помощью команды docker logs (помните, прежде чем вы сможете запустить этот контейнер, сначала должен быть запущен контейнер mongo):

01
02
03
04
05
06
07
08
09
10
11
12
MONGODB_PORT_28017_TCP_PROTO=tcp
HOSTNAME=f81d981f5e9e
MONGODB_NAME=/tomcat-maven/mongodb
MONGODB_PORT_27017_TCP=tcp://172.17.0.2:27017
MONGODB_PORT_28017_TCP_PORT=28017
MONGODB_PORT=tcp://172.17.0.2:27017
MONGODB_PORT_27017_TCP_PORT=27017
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
PWD=/local/git/docker-maven-tomcat
MONGODB_PORT_27017_TCP_PROTO=tcp
JAVA_HOME=/usr/lib/jvm/java-7-oracle MONGODB_PORT_28017_TCP_ADDR=172.17.0.2 MONGODB_PORT_28017_TCP=tcp://172.17.0.2:28017
MONGODB_PORT_27017_TCP_ADDR=172.17.0.2

Переменные среды, начинающиеся с MONGODB представляют переменные, созданные по ссылке. Как он узнал, чтобы префикс был MONGODB ? Это просто потому, что когда мы запускали контейнер mongo, мы указали необязательный параметр —name который предоставлял псевдоним имени для этого контейнера.

Теперь, как это включено в службу API? Когда мы определяем свойства Spring Data для доступа к mongo, мы указываем, что переменная окружения используется для идентификации хоста. Это делается в файле spring-data.xml (находится в spring-data.xml в src / main / resources).

01
02
03
04
05
06
07
08
09
10
threads-allowed-to-block-for-connection-multiplier="4"
                   connect-timeout="1000"
                   max-wait-time="1500"
                   auto-connect-retry="true"
                   socket-keep-alive="true"
                   socket-timeout="1500"
                   slave-ok="true"
                   write-number="1"
                   write-timeout="0"
                   write-fsync="true" />

Итак, когда мы запускаем контейнер, все, что требуется, это запустить его с помощью runDockerMaven.sh . Давайте подтвердим, что сервис работает, получив доступ к сервису через браузер, используя:

В вашей среде, если вы используете Boot2Docker, запустите boot2docker ip чтобы определить IP-адрес, на котором открыт сервис. Если вы работаете непосредственно на хосте докера, вы можете указать localhost. Это должно вернуть некоторые данные о клиентах JSON, такие как:

img8

Теперь давайте посмотрим на последний из контейнеров, который использовался для предоставления данных через веб-страницу.

Уровень веб-приложений

Уровень веб-приложений состоит из облегченного веб-сервера Python, который просто обслуживает статическую HTML-страницу вместе с соответствующим Javascript. На странице используется Google Polymer UI Framework для отображения результатов клиента в браузере. Чтобы разрешить Polymer напрямую взаимодействовать со службой API, на сервере API была настроена CORS для разрешения входящих запросов (я хотел, чтобы уровень веб-приложения был как можно более легким).

Примечание . Исходный код / ​​файлы проекта для этого уровня можно найти по адресу: https://github.com/dajevu/docker-python.

Вот соответствующий фрагмент кода из Javascript для выполнения удаленного вызова:

img9

Очевидно, что значение VMHOST присутствующее в свойстве url не является допустимым доменным именем. Вместо этого при запуске контейнера Docker это значение будет заменено фактическим IP-адресом сервера API. Прежде чем мы углубимся в детали этого, давайте сначала рассмотрим Dockerfile, используемый для сервера Python:

img10

Этот Dockerfile очень похож на другие, которые мы использовали. Как и контейнер Монго, этот основан на официальном образе Ubuntu. Затем в строках 11-14 мы указываем, что Python и связанные библиотеки загружены. В строке 23-24 мы подготавливаем каталог для исходного кода git. Затем мы извлекаем код, используя git clone в строке 27, а затем устанавливаем наш рабочий каталог в это место (строка 28). (Команда CMD в строке 31 фактически игнорируется, поскольку в сценарии запуска мы указываем, какую команду запускать (т. е. она переопределяет то, что находится в файле Docker).

Давайте посмотрим на скрипт запуска, который теперь используется для запуска нашего контейнера:

1
2
3
docker run -d -e VMHOST=192.168.59.103 --name python -p 8000:8000 \
--link tomcat-maven:tomcat jeffdavisco/polymer-python:latest \
/local/git/docker-python/run.sh

Несколько вещей, чтобы отметить выше. Обратите внимание, как я передаю IP-адрес моего экземпляра Boot2Docker, используя флаг среды -e VMHOST=192.168.59.103 . Подобно тому, что нам нужно было сделать при настройке уровня службы API, вам придется изменить это для вашей среды (если вы используете Boot2Docker, запустите boot2docker ip чтобы найти IP-адрес вашего хоста докера, или если вы работаете в Linux в boot2docker ip режиме, вы можете использовать localhost). Обратите внимание, что мы делаем еще одну вещь — выставляем порт 8000, на котором будет работать наш веб-сервер Python. Наконец, мы даем команду контейнеру docker запустить run.sh оболочки run.sh Мы посмотрим на это дальше.

run.sh содержит следующее:

1
2
3
4
5
6
#!/bin/bash
 
# replace placeholder with actual docker host
sed -i "s/VMHOST/$VMHOST/g" post-service/post-service.html
 
python -m SimpleHTTPServer 8000

Команда sed используется для замены токена-заполнителя VMHOST переменной среды, переданной в контейнер докера при запуске ( $VMHOST ). После обновления html-файла мы запускаем сервер python с помощью команды python -m SimpleHTTPServer 8000 . После запуска сценария запуска, если все прошло хорошо, вы должны иметь возможность перейти в свой браузер: http://:8000 (где dockeriphost равен вашему IP-адресу boot2docker или локальному хосту docker, если в Linux). Вы должны увидеть что-то вроде:

img11

Вы закончили урок! У вас есть докер-контейнер с Монго; другой, использующий службу API; и последний запущенный простой веб-сервис. Я надеюсь, что вам понравился этот урок, и я надеюсь, что в нем будет краткая информация о том, как с этим содержимым легче управлять, используя fig (подробнее на: http://www.fig.sh/ ).