Статьи

Создание собственных приложений для облака с помощью Spring — часть 8

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

докер-обои-черный

На данный момент, я надеюсь, вы слышали о Docker, я не собираюсь тратить здесь какое-то время на то, чтобы разобраться, что это такое, кроме того, что мы говорим, что на данный момент это технология перехода на Linux. В интернете есть масса материалов (Google — ваш друг), если вы не знакомы с Docker, но я предлагаю вам начать с официального сайта Docker., Используя Docker, мы можем запустить собственное облачное приложение практически в любом месте, единственное, что требуется от Docker — это Linux. После того, как у вас есть машина Linux с установленным демоном Docker, вы можете запустить любой контейнер Docker, включая наше собственное облачное приложение. Когда наше собственное облачное приложение запускается в контейнерах Docker, вам не нужно ничего знать о Spring, Spring Cloud или даже Java, чтобы запустить его. Кроме того, ряд поставщиков облачных вычислений (в том числе Bluemix ) поддерживают запуск контейнеров Docker на своих облачных платформах, что позволяет избежать любой блокировки поставщиков облачных вычислений.

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

Прежде чем мы начнем создавать контейнеры для различных служб в нашем приложении, вам необходимо убедиться, что у вас есть работающая среда Docker для тестирования. Docker может быть установлен на вашей любимой ОС (Windows, OSX и Linux). Зайдем к документации Докер , если вам нужно настроить среду Докер. Как только вы сможете запустить контейнер, вы сможете легко следить за остальной частью этого поста.

Dockerfiles

Для начала нам нужно создать Dockerfile для каждого из наших сервисов (рас, участников, веб, eureka и hystrix). На spring.io есть отличный пост о том, как начать работу с Docker и Spring Boot . Поскольку Spring Cloud базируется на Boot, все в этом блоге относится к нашим микросервисам. Раздел, озаглавленный «Контейнер», является наиболее релевантным разделом того, что мы пытаемся сделать с нашим приложением. В этом разделе показан пример Dockerfile, используемого для запуска приложения Spring Boot. Мы можем скопировать этот Dockerfile и слегка изменить его для наших микросервисов. Давайте начнем с добавления Dockerfile для нашего сервера Eureka.

В корне сервисного проекта Eureka (каталог, содержащий файл pom.xml) создайте новый файл с именем Dockerfile. Добавьте следующее содержимое в файл.

FROM java:8
VOLUME /tmp
ADD target/ocr-eureka*.jar app.jar
RUN bash -c 'touch /app.jar'
ENTRYPOINT ["java","-Djava.security.egd=file:/dev/./urandom","-jar","/app.jar"]

Если вы хотите больше узнать о различных командах в этом Dockerfile, взгляните на ссылку Dockerfile .

Самая важная часть файла — команда ADD, которая добавляет контейнер для нашего приложения в контейнер. С этим простым Dockerfile мы теперь сможем запустить наш сервер Eureka в контейнере. Вы можете проверить это, открыв окно терминала и перейдя в каталог, содержащий Dockerfile. Тогда беги

$ docker build -t ocr/eureka ./
$ docker run -i ocr/eureka

Команда сборки docker создаст образ Docker на основе вашего Dockerfile и пометит его в локальном реестре с помощью ocr / eureka . (После завершения сборки вы можете подтвердить, что образ находится в вашем локальном реестре, запустив  образы docker из командной строки.) Затем команда docker run запускает только что созданный образ (параметр -i делает это в интерактивном режиме, чтобы вы могли увидеть приложение протоколирование). Затем вы можете подтвердить, что ваш сервер Eureka работает, перейдя по адресу http: // <dockerhost>: 8761 . Вам нужно будет заменить Dockerhostв предыдущем URL с адресом хоста / ip вашего демона Docker. Если вы используете OSX или Windows, скорее всего, это будет виртуальная машина какого-то типа. Если вы Linux, то это может быть просто localhost.

Теперь, когда у нас есть один сервис, работающий в контейнере Docker, контейнеризация остальных сервисов должна быть легкой. Dockerfile, который мы использовали для Eureka, можно скопировать точно так же, как и для других сервисов. Единственное изменение, которое вам нужно будет сделать, — это команда ADD. Вам нужно будет изменить команду ADD, чтобы использовать правильное имя для jar службы. Например, команда ADD в Dockerfile для службы гонок будет выглядеть так:

ADD target/ocr-races*.jar app.jar

Используя Eureka Dockerfile в качестве примера, создайте Dockerfile для оставшихся сервисов (рас, участников, сети и Hystrix).

Связывание контейнеров

Если вы попытаетесь запустить другие службы в их контейнерах, вы можете заметить проблему, они не смогут зарегистрироваться в Eureka. Каждый сервис ищет Eureka на localhost: 8761, но Eureka работает в совершенно отдельном контейнере, так как сервисы общаются друг с другом? Более общий вопрос заключается в том, как связать несколько контейнеров Docker? У команды docker run есть параметр –link, в котором вы можете указать имя или идентификатор другого контейнера для ссылки. Это настроит некоторые сети между контейнерами и позволит им общаться друг с другом. Однако из-за количества контейнеров, которые у нас есть для нашего собственного облачного приложения, этот подход несколько перегружен. Лучшим вариантом является использование другого инструмента из Docker под названиемДокер сочинять . Docker Compose — это простой инструмент для организации работы нескольких контейнеров.

Docker — составь для победы

Чтобы использовать Docker Compose, нам нужно создать новый файл с именем docker-compose.yml . Этот YAML-файл сообщит Docker Compose, какие контейнеры мы хотим запустить, как мы хотим их запустить и как их связать. Я считаю, что проще всего поместить файл docker-compose.yml в корень папки, содержащей все остальные каталоги проекта, в папку с именем docker . Например, структура каталогов для приложения OCR выглядит так на моем компьютере

├── ocr-races
│   ├── Dockerfile
│   ├── pom.xml
│   ├── src
│   └── target
├── docker
│   └── docker-compose.yml
├── ocr-eureka
│   ├── Dockerfile
│   ├── pom.xml
│   ├── src
│   └── target
├── ocr-hystrix-dashboard
│   ├── Dockerfile
│   ├── mvnw
│   ├── mvnw.cmd
│   ├── pom.xml
│   ├── src
│   └── target
├── ocr-participants
│   ├── Dockerfile
│   ├── pom.xml
│   ├── src
│   └── target
└── ocr-web
    ├── Dockerfile
    ├── pom.xml
    ├── src
    └── target

Обратите внимание, что у меня есть каталог с именем docker на том же уровне, что и папки для других служб. Внутри этого каталога находится мой файл docker-compose.yml . Я предлагаю вам настроить свои проекты в той же усадьбе, однако вам не нужно. Если вы не используете ту же структуру каталогов, что и следующий файл docker-compose.yml , вам нужно будет изменить его, чтобы он соответствовал, как бы вы ни выбрали свои проекты.

Ниже мой файл docker-compose.yml .

web:
  build: ../ocr-web
  ports:
    - "8080:8080"
  environment:
    - SPRING_PROFILES_ACTIVE=docker
  links:
    - eureka
    - participants
    - races

races:
  build: ../OCR-Races
  ports:
    - "8282:8282"
  environment:
    - SPRING_PROFILES_ACTIVE=docker
  links:
    - eureka
    - participants

participants:
  build: ../ocr-participants
  ports:
    - "8181:8181"
  environment:
    - SPRING_PROFILES_ACTIVE=docker
  links:
    - eureka

hystrix:
  build: ../ocr-hystrix-dashboard
  ports:
    - "8383:8383"
  environment:
    - SPRING_PROFILES_ACTIVE=docker
  links:
    - eureka
    - races
    - participants
    - web

eureka:
  build: ../ocr-eureka
  ports:
    - "8761:8761"

Позвольте мне сначала немного рассказать об этом файле.

Свойства верхнего уровня (в данном случае eureka, web, гонки, участники и hystrix) определяют имена отдельных контейнеров. Каждый из них — это отдельный контейнер, которым Docker Compose будет управлять для нас. Под этими свойствами верхнего уровня вы увидите, что у каждого контейнера есть свойство сборки. Это свойство указывает каталог Dockerfile для контейнера (поэтому структура каталогов важна). Далее у нас есть свойство ports, которое предоставляет порты для каждого контейнера. В этом случае мы выставляем порт HTTP, который настроено для использования нашим приложением Spring Boot. У нас также есть свойство ссылки. Это указывает, какие контейнеры вы хотите связать вместе. Например, нашему контейнеру Eureka не нужно связываться ни с одним из других контейнеров, поэтому для этого контейнера нет свойства links. На противоположном конце спектра,каждый другой контейнер использует Eureka, поэтому все они ссылаются на него. Свойство link также создает для нас магию внутри контейнера. Когда вы связываете один контейнер с другим вDocker -compose.yml , Docker Compose автоматически создаст запись в файле хоста контейнера, сопоставляя IP-адрес внутреннего контейнера с именем контейнера Docker. Например, в нашем веб-контейнере файл hosts будет содержать запись для служб eureka, участники и рас. Это означает, что если нашему веб-контейнеру нужно сделать HTTP-запрос к одной из этих служб, скажем, Eureka, он будет использовать имя хоста eureka для выполнения запроса. Довольно аккуратно, верно? Это значительно упрощает связь между контейнерами. Подробнее о файлах docker-compose.yml вы можете прочитать в справочной документации .

Весенние профили для докера

Даже с Docker Compose у нас все еще есть проблема с запуском всех этих контейнеров, и это связано с Eureka. Каждый сервис должен общаться с сервером Eureka. Чтобы узнать, где работает Eureka, мы настроили каждый сервис на выполнение одной из двух вещей. Если существует переменная среды с именем VCAP_SERVICES, то служба будет использовать эту переменную среды для поиска URL-адреса сервера Eureka. Если VCAP_SERVICES недоступен, то служба предполагает, что Eureka работает на localhost: 8761. Когда мы запускаем эти сервисы в контейнере Docker, переменная среды VCAP_SERVICES отсутствует, поэтому каждый сервис предполагает, что Eureka работает на localhost: 8761, и это, очевидно, неправильно. Итак, как мы решаем эту проблему?

Вероятно, существует несколько вариантов решения проблемы, но я решил решить ее, создав другой профиль Spring Boot для служб, которые будут использоваться при запуске в контейнере Docker (мы также сделали это для решения аналогичной проблемы при запуске служб в Bluemix / Облако Литейный). Возможно, вы заметили, что для многих контейнеров в моем файле docker-compose.yml, указанном выше, для переменной среды SPRING_PROFILES_ACTIVE установлено значение docker . Это активирует профиль докера в этих приложениях, когда они работают внутри контейнера. Давайте посмотрим на мой файл application.yml для сервиса рас в качестве примера.

server:
  port: 8282

eureka:
  client:
    serviceUrl:
      defaultZone: ${vcap.services.ocr-eureka.credentials.uri:http://localhost:8761/eureka/}
  instance:
    hostname: ${vcap.application.uris[0]:localhost}
    metadataMap:
      instanceId: ${vcap.application.instance_id:${spring.application.name}:${spring.application.instance_id:${server.port}}}

---
spring:
  profiles: cloud
eureka:
  instance:
    nonSecurePort: 80

---
spring:
  profiles: docker
eureka:
  client:
    serviceUrl:
      defaultZone: http://${EUREKA_PORT_8761_TCP_ADDR}:8761/eureka/
  instance:
    preferIpAddress: true

Здесь вы можете видеть, что я добавил новый профиль под названием Docker. Этот профиль пересекает некоторые настройки Eureka. Сначала мы переопределяем местоположение сервера Eureka, используя свойство defaultZone . Вы заметите, что в URL-адресе используется переменная среды EUREKA_PORT_8761_TCP_ADDR. Откуда эта переменная среды? Это на самом деле происходит от Docker Compose . Помимо создания записей в файле хоста контейнера для всех связанных контейнеров, Docker Compose также создает переменные среды, содержащие такие вещи, как IP-адрес связанного контейнера. Так как мы можем сделать подстановку переменных окружения в нашем application.ymlфайл, я подумал, что было бы удобно использовать эту переменную окружения, чтобы указать местоположение сервера Eureka при работе в контейнере Docker. В этом подходе есть некоторая хрупкость: если мы изменим имя контейнера Eureka в нашем файле Docker Compose, это, в свою очередь, изменит имя переменной среды, и эта конфигурация будет нарушена. Однако для этого простого примера этот подход работает просто отлично.

Вы также заметите, что в профиле Docker мы устанавливаем предпочитаемый адрес IP-адреса в true . Без этой конфигурации служба Races регистрирует имя хоста в Eureka на основе того, что возвращает java.net.InetAddress. В среде Docker другие сервисы не будут знать, что это за имя хоста, поэтому нам нужно убедиться, что когда другие сервисы спрашивают Eureka, где находится сервис гонок, мы используем IP-адрес, который Docker назначил контейнеру. Другое решение этой проблемы — установить для свойства хоста Eureka имя хоста, которое мы настроили для контейнера в нашем файле Docker Compose. Однако, если бы мы сделали это, мы бы добавили ту же хрупкость, что и выше, при настройке сервера Eureka.

Этот же профиль можно добавить в файл application.yml всех других служб, которые должны взаимодействовать с Eureka (hystrix, участники и Интернет).

Проверьте это!

Теперь, когда у нас есть эти дополнительные профили, мы можем на самом деле протестировать их с помощью Docker Compose. В окне вашего терминала перейдите в каталог, содержащий ваш файл docker-compose.yml . Затем выполните следующую команду

$ docker-compose build

Это создаст образы для всех контейнеров в файле docker-compose.yml (при необходимости). Тогда беги

$ docker-compose up

Это запустит все контейнеры, определенные в docker-compose.ymlфайл. Вам нужно будет дождаться запуска всех контейнеров и зарегистрироваться в Eureka, прежде чем вы все протестируете. Как только все началось, перейдите по адресу http: // <dockerhost>: 8080. Приложение должно работать как обычно. Вы также должны быть в состоянии использовать Hystrix и Turbine с некоторыми небольшими изменениями. Вы можете перейти на панель инструментов Hystrix, перейдя по адресу http: // <dockerhost>: 8383 / hystrix. Если вы хотите посмотреть на автоматические выключатели для веб-службы, вам нужно указать Hystrix на веб-контейнер. Помните, что с точки зрения контейнера, в котором работает Hystrix, доступ к веб-контейнеру можно получить с помощью имени хоста web. Чтобы сделать это, URL-адрес потока, который необходимо ввести для мониторинга этих автоматических выключателей, — это http: // web: 8080 / hystrix.stream. Ваша первоначальная мысль может состоять в том, чтобы ввести http: // <dockerhost>: 8080 / hystrix.stream,но контейнер Hystrix — это тот, который выполняет запрос к веб-контейнеру, и он не знает, что такое <dockerhost>, но он знает, что к веб-контейнеру можно получить доступ, используя имя хоста web. То же самое верно и для службы гонок: для просмотра автоматических выключателей вы должны ввести URL-адрес http: // races: 8282 / hystrix.stream. Для Turbine вы должны использовать http: // localhost: 8383 / turbine.stream? Cluster = RACES для гонок или http: // localhost: 8383 / turbine.stream? Cluster = WEB для веб. Мы снова используем URL localhost, потому что Turbine работает внутри контейнера Hystrix, поэтому ему просто нужно поговорить с самим собой.чтобы просмотреть автоматические выключатели, вы должны ввести URL http: // races: 8282 / hystrix.stream. Для Turbine вы должны использовать http: // localhost: 8383 / turbine.stream? Cluster = RACES для гонок или http: // localhost: 8383 / turbine.stream? Cluster = WEB для веб. Мы снова используем URL localhost, потому что Turbine работает внутри контейнера Hystrix, поэтому ему просто нужно поговорить с самим собой.чтобы просмотреть автоматические выключатели, вы должны ввести URL http: // races: 8282 / hystrix.stream. Для Turbine вы должны использовать http: // localhost: 8383 / turbine.stream? Cluster = RACES для гонок или http: // localhost: 8383 / turbine.stream? Cluster = WEB для веб. Мы снова используем URL localhost, потому что Turbine работает внутри контейнера Hystrix, поэтому ему просто нужно поговорить с самим собой.

Наше собственное облачное приложение теперь работает внутри набора контейнеров Docker, что делает наше приложение гораздо более переносимым, чем раньше. Следующим шагом, который вы, возможно, захотите предпринять, является развертывание образов для всех контейнеров в некоторый тип реестра Docker, будь то DockerHub или частный реестр (см. Свойство изображения в справочной документации Docker Compose). Как только изображения появятся в реестре, вы можете изменить свой файл Docker Compose, чтобы извлечь эти изображения из реестра, а не создавать их локально. Это снова делает приложение чрезвычайно портативным, потому что все, что вам нужно для запуска приложения, — это файл Docker Compose.