Как создать и запустить сервис в кластере CoreOS

Вступление

Одним из основных преимуществ CoreOS является возможность управлять службами всего кластера из одной точки. Платформа CoreOS предоставляет интегрированные инструменты для упрощения этого процесса.

В этом руководстве мы продемонстрируем типичный рабочий процесс для запуска служб в ваших кластерах CoreOS. Этот процесс продемонстрирует несколько простых практических способов взаимодействия с некоторыми из наиболее интересных утилит CoreOS для настройки приложения.

Предпосылки и цели

Чтобы начать работу с этим руководством, у вас должен быть кластер CoreOS с минимум тремя настроенными машинами. Вы можете следовать нашему guid для начальной загрузки кластера CoreOS здесь.

Ради этого руководства наши три узла будут выглядеть следующим образом:

  • coreos-1

  • coreos-2

  • coreos-3

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

В этом руководстве мы рассмотрим базовый рабочий процесс получения служб, работающих в кластере CoreOS. В демонстрационных целях мы будем настраивать простой веб-сервер Apache. Мы рассмотрим настройку контейнерной среды обслуживания с помощью Docker, а затем создадим файл модуля в стиле systemd, чтобы описать службу и ее рабочие параметры.

В файле сопутствующего модуля мы сообщим нашему сервису зарегистрироваться в etcd, что позволит другим сервисам отслеживать его данные. Мы представим оба наших сервиса флоту, где мы сможем запускать и управлять сервисами на машинах всего нашего кластера.

Подключитесь к узлу и передайте свой агент SSH

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

Для работы инструмента + fleetctl +, который мы будем использовать для связи с соседними узлами, нам нужно передать информацию нашего агента SSH при подключении.

Перед подключением через SSH вы должны запустить свой агент SSH. Это позволит вам перенаправить свои учетные данные на сервер, к которому вы подключаетесь, и вы сможете войти с этого компьютера на другие узлы. Чтобы запустить пользовательский агент на вашем компьютере, вы должны набрать:

eval $(ssh-agent)

Затем вы можете добавить свой закрытый ключ в хранилище памяти агента, набрав:

ssh-add

На этом этапе ваш агент SSH должен быть запущен, и он должен знать о вашем закрытом ключе SSH. Следующим шагом является подключение к одному из узлов в вашем кластере и пересылка информации вашего агента SSH. Вы можете сделать это, используя флаг + -A +:

ssh -A core@

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

Создание Docker-контейнера

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

Для этого руководства мы создадим образ с использованием первого метода, поскольку он более прост для тех, кто не знаком с Docker. Перейдите по этой ссылке, если вы хотите узнать больше о том, как https://www.digitalocean.com/community/tutorials/docker-explained-using-dockerfiles-to-automate-building-of-images[build изображение Docker из Dockerfile. Наша цель - установить Apache на базовый образ Ubuntu 14.04 в Docker.

Прежде чем начать, вам необходимо войти в систему или зарегистрироваться в реестре Docker Hub. Для этого введите:

docker login

Вам будет предложено указать имя пользователя, пароль и адрес электронной почты. Если вы выполняете это в первый раз, учетная запись будет создана с использованием предоставленных вами данных, и на указанный адрес будет отправлено электронное письмо с подтверждением. Если вы уже создали учетную запись в прошлом, вы войдете в систему с указанными учетными данными.

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

docker run -i -t ubuntu:14.04 /bin/bash

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

  • * run *: Это говорит Docker, что мы хотим запустить контейнер с параметрами, которые следуют.

  • * -i *: запуск контейнера Docker в интерактивном режиме. Это обеспечит доступность STDIN для среды контейнера, даже если он не подключен.

  • * -t *: Это создает псевдо-TTY, позволяя нам терминальный доступ к среде контейнера.

  • * Ubuntu: 14.04 *: Это комбинация репозитория и образа, которую мы хотим запустить. В этом случае мы запускаем Ubuntu 14.04. Изображение хранится в Ubuntu хранилище Docker в Docker Hub.

  • * / bin / bash *: это команда, которую мы хотим запустить в контейнере. Поскольку нам нужен терминальный доступ, нам нужно порождать сеанс оболочки.

Слои базового изображения будут удалены из реестра Docker Hub, и начнется сеанс bash. Вы попадете в итоговую сессию оболочки.

Отсюда мы можем продолжить создание нашей сервисной среды. Мы хотим установить веб-сервер Apache, поэтому мы должны обновить наш локальный индекс пакета и установить через + apt +:

apt-get update
apt-get install apache2

После завершения установки мы можем отредактировать файл по умолчанию + index.html +:

echo "<h1>Running from Docker on CoreOS</h1>" > /var/www/html/index.html

Когда вы закончите, вы можете выйти из сеанса bash обычным способом:

exit

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

docker ps -l
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS                      PORTS               NAMES
cb58a2ea1f8f        ubuntu:14.04        "/bin/bash"         8 minutes ago       Exited (0) 55 seconds ago                       jovial_perlman

Нам нужен столбец «КОНТЕЙНЕР ID». В приведенном выше примере это будет + cb58a2ea1f8f +. Чтобы потом иметь возможность раскручивать тот же контейнер со всеми внесенными вами изменениями, вам необходимо зафиксировать эти изменения в хранилище вашего имени пользователя. Вам нужно будет также выбрать имя для изображения.

В наших целях мы будем притворяться, что имя пользователя - + user_name +, но вы должны заменить его именем учетной записи Docker Hub, в которую вы вошли немного назад. Мы назовем наше изображение + apache. Команда для фиксации изменений изображения:

docker commit  /apache

Это сохранит изображение, чтобы вы могли вспомнить текущее состояние контейнера. Вы можете проверить это, набрав:

docker images
REPOSITORY           TAG                 IMAGE ID            CREATED             VIRTUAL SIZE
user_name/apache     latest              42a71fb973da        4 seconds ago       247.4 MB
ubuntu               14.04               c4ff7513909d        3 weeks ago         213 MB

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

docker push /apache

Теперь у вас есть образ контейнера, настроенный с вашим экземпляром Apache.

Создание файла сервисного модуля Apache

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

Fleet управляет планированием услуг для всего кластера CoreOS. Он предоставляет пользователю централизованный интерфейс, а также локально управляет системами инициализации systemd каждого хоста для выполнения соответствующих действий.

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

Для начала создайте файл с именем + apache @ .service + в вашем домашнем каталоге. + @ + Указывает, что это файл службы шаблонов. Мы немного разберемся, что это значит. Образ CoreOS поставляется с текстовым редактором + vim +:

Чтобы начать определение сервиса, мы создадим заголовок раздела + [Unit] + и настроим некоторые метаданные об этом модуле. Мы включим описание и уточним информацию о зависимостях. Поскольку наш модуль должен быть запущен после того, как доступны и etcd, и Docker, нам нужно определить это требование.

Нам также нужно добавить другой файл сервиса, который мы будем создавать как требование. Этот второй файл сервиса будет отвечать за обновление etcd информацией о нашем сервисе. Требование этого здесь заставит это начаться, когда эта служба запущена. Мы объясним +% i + в названии сервиса позже:

[Unit]
Description=Apache web server service
After=etcd.service
After=docker.service
Requires=apache-discovery@%i.service

Далее нам нужно сообщить системе, что должно произойти при запуске или остановке данного устройства. Мы делаем это в разделе + [Service] +, так как мы настраиваем службу.

Первое, что мы хотим сделать, это отключить запуск службы от тайм-аута. Поскольку наши сервисы являются контейнерами Docker, при первом запуске на каждом хосте образ должен быть удален с серверов Docker Hub, что может привести к более длительному, чем обычно, времени запуска при первом запуске.

Мы хотим установить для + KillMode + значение «none», чтобы systemd позволила нашей команде «stop» завершить процесс Docker. Если мы пропустим это, systemd будет думать, что процесс Docker завершился неудачно, когда мы вызываем нашу команду stop.

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

Нам нужно будет уничтожить все оставшиеся контейнеры с именем, которое мы хотим использовать, а затем удалить их. Именно в этот момент мы фактически извлекаем изображение из Docker Hub. Мы также хотим получить файл + / etc / environment +. Это включает в себя переменные, такие как публичные и частные IP-адреса хоста, на котором запущена служба:

[Unit]
Description=Apache web server service
After=etcd.service
After=docker.service
Requires=apache-discovery@%i.service

[Service]
TimeoutStartSec=0
KillMode=none
EnvironmentFile=/etc/environment
ExecStartPre=-/usr/bin/docker kill apache%i
ExecStartPre=-/usr/bin/docker rm apache%i
ExecStartPre=/usr/bin/docker pull /apache

Синтаксис + = - + для первых двух строк + ExecStartPre + указывает на то, что эти строки подготовки могут потерпеть неудачу, а файл модуля будет продолжаться. Поскольку эти команды выполняются успешно, только если контейнер с таким именем существует, они не будут выполнены, если контейнер не найден.

Возможно, вы заметили суффикс +% i + в конце имен контейнеров apache в приведенных выше директивах. Файл службы, который мы создаем, на самом деле представляет собой template unit-файл. Это означает, что при запуске файла автопарк автоматически заменит некоторую информацию соответствующими значениями. Прочитайте информацию по предоставленной ссылке, чтобы узнать больше.

В нашем случае +% i + будет заменен везде, где он существует в файле, с частью имени файла службы справа от + @ + перед суффиксом + .service +. Наш файл просто называется + apache @ .service +.

Хотя мы отправим файл в + fleetctl + с помощью + apache @ .service +, когда мы загрузим файл, мы загрузим его как + apache @ .service +, где «PORT_NUM» будет портом, который мы хотим чтобы запустить этот сервер. Мы будем маркировать наш сервис в зависимости от порта, на котором он будет работать, чтобы мы могли легко их дифференцировать.

Далее нам нужно запустить настоящий контейнер Docker:

[Unit]
Description=Apache web server service
After=etcd.service
After=docker.service
Requires=apache-discovery@%i.service

[Service]
TimeoutStartSec=0
KillMode=none
EnvironmentFile=/etc/environment
ExecStartPre=-/usr/bin/docker kill apache%i
ExecStartPre=-/usr/bin/docker rm apache%i
ExecStartPre=/usr/bin/docker pull /apache
ExecStart=/usr/bin/docker run --name apache%i -p ${COREOS_PUBLIC_IPV4}:%i:80 /apache /usr/sbin/apache2ctl -D FOREGROUND

Мы вызываем обычную команду + docker run + и передаем ей некоторые параметры. Мы передаем это имя в том же формате, который мы использовали выше. Мы также собираемся предоставить порт из нашего контейнера Docker общедоступному интерфейсу нашего хост-компьютера. Номер порта хост-машины будет взят из переменной +% i +, которая фактически позволяет нам указать порт.

Мы будем использовать переменную + COREOS_PUBLIC_IPV4 + (взятую из файла среды, который мы поставили), чтобы быть явным для интерфейса хоста, который мы хотим связать. Мы могли бы это пропустить, но это настраивает нас на простую модификацию позже, если мы хотим изменить это на частный интерфейс (например, если мы балансируем нагрузку).

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

Далее нам нужно указать команду для вызова, когда служба должна быть остановлена. Мы просто остановим контейнер. Очистка контейнера выполняется при каждом перезапуске.

Мы также хотим добавить раздел под названием + [X-Fleet] +. Этот раздел специально разработан, чтобы дать инструкции для парка о том, как планировать обслуживание. Здесь вы можете добавить ограничения, чтобы ваша служба работала или не работала в определенных условиях в отношении других служб или состояний компьютеров.

Мы хотим, чтобы наш сервис работал только на хостах, на которых еще не запущен веб-сервер Apache, поскольку это позволит нам легко создавать высокодоступные сервисы. Мы будем использовать подстановочный знак для перехвата любых файлов службы apache, которые у нас могут быть запущены:

[Unit]
Description=Apache web server service
After=etcd.service
After=docker.service
Requires=apache-discovery@%i.service

[Service]
TimeoutStartSec=0
KillMode=none
EnvironmentFile=/etc/environment
ExecStartPre=-/usr/bin/docker kill apache%i
ExecStartPre=-/usr/bin/docker rm apache%i
ExecStartPre=/usr/bin/docker pull /apache
ExecStart=/usr/bin/docker run --name apache%i -p ${COREOS_PUBLIC_IPV4}:%i:80 /apache /usr/sbin/apache2ctl -D FOREGROUND
ExecStop=/usr/bin/docker stop apache%i

[X-Fleet]
X-Conflicts=apache@*.service

На этом мы закончили с файлом нашего сервера Apache. Теперь мы создадим файл сопутствующего сервиса для регистрации сервиса в etcd.

Регистрация сервисных состояний с помощью Etcd

Чтобы записать текущее состояние служб, запущенных в кластере, мы хотим записать некоторые записи в etcd. Это известно как регистрация в etcd.

Чтобы сделать это, мы запустим минимальный сопутствующий сервис, который может обновлять etcd, когда сервер доступен для трафика.

Новый файл службы будет называться + apache-discovery @ .service +. Откройте его сейчас:

Мы начнем с раздела + [Unit] +, как и прежде. Мы опишем назначение службы, а затем создадим директиву + BindsTo +.

Директива + BindsTo + определяет зависимость, которую этот сервис ищет для информации о состоянии. Если указанная служба остановлена, то блок, который мы сейчас пишем, также остановится. Мы будем использовать это для того, чтобы в случае непредвиденного сбоя нашего веб-сервера эта служба обновляла etcd для отражения этой информации. Это решает потенциальную проблему наличия устаревшей информации в etcd, которая может быть ошибочно использована другими службами:

[Unit]
Description=Announce Apache@%i service
BindsTo=apache@%i.service

Для раздела + [Service] + мы хотим снова получить файл среды с информацией об IP-адресе хоста.

Для фактической команды запуска мы хотим запустить простой бесконечный цикл bash. Внутри цикла мы будем использовать команду + etcdctl +, которая используется для изменения значений etcd, чтобы установить ключ в хранилище etcd в + / announce / services / apache% i +. +% I + будет заменен разделом имени службы, который мы будем загружать между суффиксом + @ + и + .service +, который снова будет номером порта службы Apache.

Значение этого ключа будет установлено на публичный IP-адрес узла и номер порта. Мы также установим время истечения 60 секунд для значения, чтобы ключ был удален, если служба каким-либо образом погибнет. Затем мы будем спать 45 секунд. Это обеспечит совпадение с истечением срока действия, так что мы всегда обновляем значение TTL (время жизни), прежде чем оно достигнет своего тайм-аута.

Для действия остановки мы просто удалим ключ той же утилитой + etcdctl +, отметив службу как недоступную:

[Unit]
Description=Announce Apache@%i service
BindsTo=apache@%i.service

[Service]
EnvironmentFile=/etc/environment
ExecStart=/bin/sh -c "while true; do etcdctl set /announce/services/apache%i ${COREOS_PUBLIC_IPV4}:%i --ttl 60; sleep 45; done"
ExecStop=/usr/bin/etcdctl rm /announce/services/apache%i

Последнее, что нам нужно сделать, это добавить условие, чтобы эта служба запускалась на том же хосте, что и веб-сервер, о котором она сообщает. Это гарантирует, что если хост выйдет из строя, информация etcd изменится соответствующим образом:

[Unit]
Description=Announce Apache@%i service
BindsTo=apache@%i.service

[Service]
EnvironmentFile=/etc/environment
ExecStart=/bin/sh -c "while true; do etcdctl set /announce/services/apache%i ${COREOS_PUBLIC_IPV4}:%i --ttl 60; sleep 45; done"
ExecStop=/usr/bin/etcdctl rm /announce/services/apache%i

[X-Fleet]
X-ConditionMachineOf=apache@%i.service

Теперь у вас есть служба поддержки, которая может записывать текущее состояние вашего сервера Apache в etcd.

Работа с модульными файлами и флотом

Теперь у вас есть два сервисных шаблона. Мы можем отправить их непосредственно в + fleetctl +, чтобы наш кластер знал о них:

Вы должны увидеть ваши новые служебные файлы, набрав:

fleetctl list-unit-files
UNIT                HASH    DSTATE      STATE       TMACHINE
[email protected]   26a893f inactive    inactive    -
[email protected]         72bcc95 inactive    inactive    -

Шаблоны теперь существуют в нашей общекластерной системе инициализации.

Поскольку мы используем шаблоны, которые зависят от того, запланированы ли они на определенных хостах, нам нужно загрузить файлы дальше. Это позволит нам указать новое имя для этих файлов с номером порта. Это когда + fleetctl + просматривает раздел + [X-Fleet] +, чтобы увидеть, каковы требования к планированию.

Поскольку мы не делаем никакой балансировки нагрузки, мы просто запустим наш веб-сервер на порту 80. Мы можем загрузить каждый сервис, указав его между суффиксом + @ + и суффиксом + .service +:

fleetctl load [email protected]
fleetctl load [email protected]

Вы должны получить информацию о том, на каком хосте в вашем кластере загружается служба:

Unit [email protected] loaded on 41f4cb9a.../10.132.248.119
Unit [email protected] loaded on 41f4cb9a.../10.132.248.119

Как видите, обе эти службы были загружены на одну и ту же машину, что мы и указали. Поскольку наш файл службы + apache-discovery + привязан к нашей службе Apache, мы можем просто запустить более позднюю версию для запуска обеих наших служб:

fleetctl start [email protected]

Теперь, если вы спросите, какие модули работают в нашем кластере, мы должны увидеть следующее:

fleetctl list-units
UNIT                MACHINE             ACTIVE  SUB
[email protected] 41f4cb9a.../10.132.248.119  active  running
[email protected]       41f4cb9a.../10.132.248.119  active  running

Похоже, что наш веб-сервер запущен и работает. В нашем сервисном файле мы указали Docker связываться с публичным IP-адресом хост-сервера, но IP-адрес, отображаемый с помощью + fleetctl +, является частным адресом (потому что мы передали + $ private_ipv4 + в облачной конфигурации при создании этого примера кластер).

Однако мы зарегистрировали общедоступный IP-адрес и номер порта с помощью etcd. Чтобы получить значение, вы можете использовать утилиту + etcdctl + для запроса значений, которые мы установили. Если вы помните, ключи, которые мы установили, были + / announce / services / apache. Итак, чтобы узнать подробности нашего сервера, введите:

etcdctl get /announce/services/apache80
104.131.15.192:80

Если мы посетим эту страницу в нашем веб-браузере, мы увидим очень простую страницу, которую мы создали:

изображение: https: //assets.digitalocean.com/articles/coreos_basic/web_page.png [Основная веб-страница CoreOS]

Наш сервис был успешно развернут. Давайте попробуем загрузить другой экземпляр, используя другой порт. Следует ожидать, что веб-сервер и связанный с ним контейнер-помощник будут запланированы на одном хосте. Однако из-за нашего ограничения в файле службы Apache мы должны ожидать, что этот хост будет different от того, который обслуживает нашу службу порта 80.

Давайте загрузим сервис, работающий на порту 9999:

Unit [email protected] loaded on 855f79e4.../10.132.248.120
Unit [email protected] loaded on 855f79e4.../10.132.248.120

Мы видим, что оба новых сервиса были запланированы на одном и том же новом хосте. Запустите веб-сервер:

fleetctl start [email protected]

Теперь мы можем получить публичный IP-адрес этого нового хоста:

etcdctl get /announce/services/apache9999
104.131.15.193:9999

Если мы посетим указанный адрес и номер порта, мы должны увидеть другой веб-сервер:

изображение: https: //assets.digitalocean.com/articles/coreos_basic/web_page.png [Основная веб-страница CoreOS]

Теперь мы развернули два веб-сервера в нашем кластере.

Если вы остановите веб-сервер, контейнер-помощник также должен остановиться:

fleetctl stop [email protected]
fleetctl list-units
UNIT                MACHINE             ACTIVE      SUB
[email protected] 41f4cb9a.../10.132.248.119  inactive    dead
[email protected]   855f79e4.../10.132.248.120  active  running
[email protected]       41f4cb9a.../10.132.248.119  inactive    dead
[email protected]     855f79e4.../10.132.248.120  active  running

Вы можете проверить, что ключ etcd был также удален:

etcdctl get /announce/services/apache80
Error:  100: Key not found (/announce/services/apache80) [26693]

Кажется, это работает точно так, как ожидалось.

Заключение

Следуя этому руководству, вы должны быть знакомы с некоторыми из распространенных способов работы с компонентами CoreOS.

Мы создали наш собственный контейнер Docker с установленным внутри него сервисом, который мы хотели запустить, и создали файл модуля флота, чтобы сообщить CoreOS, как управлять нашим контейнером. Мы внедрили вспомогательный сервис, чтобы постоянно обновлять наше хранилище данных etcd с информацией о состоянии нашего веб-сервера. Мы управляли нашими услугами с помощью fleetctl, планируя услуги на разных хостах.

В последующих руководствах мы продолжим изучение некоторых областей, которые мы кратко затронули в этой статье.

Related