Наши партнеры

UnixForum





Библиотека сайта rus-linux.net

Масштабирование Docker с помощью Kubernetes

Оригинал: Scaling Docker with Kubernetes
Автор: Carlos Sanchez
Дата публикации: November 29, 2014
Перевод: Н.Ромоданов
Дата перевода: март 2015 г.

Kubernetes является проектом с открытым исходным кодом, предназначенным для управления кластером контейнеров Linux как единым целым. Он позволяет управлять и запускать контейнеры Docker на большом количестве хостов и поддерживает совместное размещение большого количества контейнеров, работу с сервисами и управление репликациями. Проект был начат компанией Google и теперь поддерживается многими компаниями, среди которых Microsoft, RedHat, IBM и Docker.

Компания Google пользуется контейнерной технологией уже более десяти лет и она начинала с запуска более 2 млрд контейнеров в течение одной недели. С помощью проекта Kubernetes она одновременно управляла своими контейнерами и получала практический опыт создания открытой платформы, предназначенной для масштабируемого запуска контейнеров.

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

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

Концепции Kubernetes

С точки зрения архитектуры проект Kubernetes состоит из главного сервера (master server) и большого количества подчиненных серверов, которые называются миньонами (minions). С помощью инструментальных средств командной строки осуществляется подключения к API на главном сервере, который управляет и дирижирует работой всех миньонов - хостов Docker, получающих инструкции от главного сервера и запускающих контейнеры.

  • Главный сервер Master: Сервер с сервисом Kubernetes API. Предполагается, что в будущем будут конфигурации с несколькими главными серверами.
  • Сервер Minion: Любой из многочисленных хостов Docker с сервисом Kubelet, который получает задания от главного сервера master и управляют тем, как контейнеры запускаются на конкретном хосте.
  • Оболочка Pod: Определяет коллекцию контейнеров, связанных друг с другом, которые развертываются на том же самом миньоне, например, контейнер с базой данных и веб-сервером.
  • Контроллер репликаций: Определяет, какое количество должно быть запущено оболочек pod или контейнеров. Контейнеры размещаются на многочисленных серверах minion.
  • Сервис: Определяет, как внутри контейнера публикуются сервисы / порты и происходит взаимодействие через прокси со внешней средой. Сервис отображает связь между портами контейнеров, работающих внутри оболочек pod на различных серверах minion, и портами, которые доступны извне.
  • kubecfg: Клиентская программа, работающая из командной строки, которая подключается к главному серверу master для администрирования системы Kubernetes.

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

Примером приложения, используемого в этой статье, является сервер непрерывной интеграции Jenkins CI с типовым распределением заданий «главный - подчиненные». Jenkins сконфигурирован с помощью плагина Jenkins swarm plugin так, чтобы был запущен главный сервер и подчиненные сервера Jenkins, причем все они работают на нескольких хостах как контейнеры Docker. Подчиненные сервера при запуске подключаются к главному серверу Jenkins и могут получать от него задания. Конфигурационные файлы, используемые в примере доступны на GitHub, а образы Docker можно взять как образ csanchez/jenkins-swarm - для главного сервера Jenkins, который представляет собой расширение официального образа Jenkins с плагином swarm, и как образы csanchez/jenkins-swarm-slave - для каждого подчиненного сервера, просто работающего как подчиненный сервис в контейнере JVM.

Создание кластера Kubernetes

В Kubernetes предоставляются скрипты для создания кластера с несколькими операционными системами и несколькими облачными / виртуальными провайдерами: Vagrant (применяется для локального тестирования), Google Compute Engine, Azure, Rackspace и т.д.

В примерах использоваться локальный кластер с Vagrant и Fedora в качестве ОС, так как это описано в инструкциях, описывающих начало работы; использовалась версия Kubernetes 0.5.4. Вместо того, чтобы запускать три сервера minion (три хоста Docker) так, как это задано по умолчанию, мы запустим лишь два, которых достаточно для того, чтобы показать возможности Kubernetes и не изпользовать более мощную машину.

После того, как у вас будет скачанный пакет Kubernetes и вы раскроете каталог, то из него можно запустить примеры. Чтобы с нуля создать кластер, потребуется только команда ./cluster/kube-up.sh.

$ export KUBERNETES_PROVIDER=vagrant
$ export KUBERNETES_NUM_MINIONS=2
$ ./cluster/kube-up.sh

Получите конфигурационные файлы примеров:

$ git clone https://github.com/carlossg/kubernetes-jenkins.git

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

Инструментальные средства командной строки

Инструмент, работающий из командной строки и предназначенный для взаимодействия с Kubernetes, называется kubecfg, есть также удобный скрипт cluster/kubecfg.sh.

Чтобы проверить, что наш кластер запущен и работает с двумя серверами minion, просто запустите команду kubecfg list minions и в конфигурации Vagrant должны быть показаны две виртуальные машины.

$ ./cluster/kubecfg.sh list minions

Minion identifier
----------
10.245.2.2
10.245.2.3

Оболочки Pod

Сервер Jenkins master определяется в терминологии Kubernetes как оболочка pod. В оболочке pod можно указать несколько контейнеров, которые будут развернуты на одном и том же хосте Docker; преимущество в этом случае состоит в том, что контейнеры, находящиеся в одной оболочке pod, могут вместе пользоваться одними и теми же ресурсами, такими как тома хранения данных, и тем же самый пространством имен сети и адресом IP. Тома, являющиеся по умолчанию пустыми каталогами, имеют тип emptyDir; они существуют в течение всего времени, пока существует оболочка pod, поскольку в контейнерах нет механизма постоянного хранения данных. Что касается томов другого типа - hostDir, то каталоги, которые будут монтироваться в контейнере, находятся непосредственно на сервере хостовой системы.

В этом конкретном примере с серверами Jenkins мы могли бы использовать оболочку pod с двумя контейнерами, сервером Jenkins и, например, контейнер MySQL для того, чтобы пользоваться базой данных, но мы рассмотрим лишь отдельно работающий главный сервер Jenkins master.

Для того чтобы создать оболочку pod для Jenkins, мы запускаем скрипт kubecfg, в котором определена оболочка pod контейнера Jenkins и воспользуемся образом Docker csanchez/jenkins-swarm, и определены порты 8080 и 50000, отображаемые в контейнер с тем, чтобы можно было получить доступ к веб-интерфейсу Jenkins и интерфейсу API, а также воспользуемся томом, смонтированный /var/jenkins_home. Пример кода также можно взять на GitHub.

Оболочка pod веб-интерфейса Jenkins (pod.json) определяется следующим образом:

{
  "id": "jenkins",
  "kind": "Pod",
  "apiVersion": "v1beta1",
  "desiredState": {
    "manifest": {
      "version": "v1beta1",
      "id": "jenkins",
      "containers": [
        {
          "name": "jenkins",
          "image": "csanchez/jenkins-swarm:1.565.3.3",
          "ports": [
            {
              "containerPort": 8080,
              "hostPort": 8080
            },
            {
              "containerPort": 50000,
              "hostPort": 50000
            }
          ],
          "volumeMounts": [
            {
              "name": "jenkins-data",
              "mountPath": "/var/jenkins_home"
            }
          ]
        }
      ],
      "volumes": [
        {
          "name": "jenkins-data",
          "source": {
            "emptyDir": {}
          }
        }
      ]
    }
  },
  "labels": {
    "name": "jenkins"
  }
}

И создается с помощью:

$ ./cluster/kubecfg.sh -c kubernetes-jenkins/pod.json create pods

Name                Image(s)                           Host                Labels              Status
----------          ----------                         ----------          ----------          ----------
jenkins             csanchez/jenkins-swarm:1.565.3.3   <unassigned>        name=jenkins        Pending

Через некоторое время, которое зависит от вашего интернет-соединения, поскольку в сервер minion должен загрузиться образ Docker, мы сможем проверить то, как был запущен сервер minion.

$ ./cluster/kubecfg.sh list pods
Name                Image(s)                           Host                    Labels              Status
----------          ----------                         ----------              ----------          ----------
jenkins             csanchez/jenkins-swarm:1.565.3.3   10.0.29.247/10.0.29.247   name=jenkins        Running

Если в сервере minion (minion-1 или minion-2), на который назначена оболочка pod, есть ssh, то мы можем увидеть как среди других контейнеров, используемых Kubernetes для внутренних нужд (kubernetes/pause и google/cadvisor) был запущен определенный нами контейнер Docker.

И, раз мы знаем идентификатор контейнера, мы можем просматривать журналы контейнера с помощью команды vagrant ssh minion-1 -c "docker logs cec3eab3f4d3".

Мы должны также найти веб-интерфейс Jenkins на порту http://10.245.2.2:8080/ или http://10.0.29.247:8080/, что зависит от того, какой сервер minion был запущен.

Сервисы

В Kubernetes можно определить сервисы, используемых для поиска сервисов и обращений к прокси соответствующего сервера minion. Согласно следующему определению, заданному в service-http.json, мы создаем сервис с идентификатором jenkins, указывающим на оболочку pod с меткой name=jenkins так, как это объявлено в определении оболочки pod, и пробрасываем порт 8888 на порт 8080 контейнера.

{
  "id": "jenkins",
  "kind": "Service",
  "apiVersion": "v1beta1",
  "port": 8888,
  "containerPort": 8080,
  "selector": {
    "name": "jenkins"
  }
}

Создание сервиса с помощью kubecfg:

$ ./cluster/kubecfg.sh -c kubernetes-jenkins/service-http.json create services

Name                Labels              Selector            IP                  Port
----------          ----------          ----------          ----------          ----------
jenkins                                 name=jenkins        10.0.29.247         8888

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

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

JENKINS_PORT='tcp://10.0.29.247:8888'
JENKINS_PORT_8080_TCP='tcp://10.0.29.247:8888'
JENKINS_PORT_8080_TCP_ADDR='10.0.29.247'
JENKINS_PORT_8080_TCP_PORT='8888'
JENKINS_PORT_8080_TCP_PROTO='tcp'
JENKINS_SERVICE_PORT='8888'
SERVICE_HOST='10.0.29.247'

Для того, чтобы открыть порт 50000, который требуется для плагина Jenkins swarm, нам также потребуется еще одна настройка. Ее можно сделать с помощью создания еще одного сервиса service-slave.json, и Kubernetes будет перенаправлять на этот порт трафик, предназначенный для сервера контейнера Jenkins.

{
  "id": "jenkins-slave",
  "kind": "Service",
  "apiVersion": "v1beta1",
  "port": 50000,
  "containerPort": 50000,
  "selector": {
    "name": "jenkins"
  }
}

Сервис снова создается с помощью kubecfg.

$ ./cluster/kubecfg.sh -c kubernetes-jenkins/service-slave.json create services

Name                Labels              Selector            IP                  Port
----------          ----------          ----------          ----------          ----------
jenkins-slave                           name=jenkins        10.0.86.28          50000

Теперь есть все необходимые сервисы, в том числе некоторые, являющиеся внутренними для Kubernetes:

$ ./cluster/kubecfg.sh list services

Name                Labels              Selector                                  IP                  Port
----------          ----------          ----------                                ----------          ----------
kubernetes-ro                           component=apiserver,provider=kubernetes   10.0.22.155         80
kubernetes                              component=apiserver,provider=kubernetes   10.0.72.49          443
jenkins                                 name=jenkins                              10.0.29.247         8888
jenkins-slave                           name=jenkins                              10.0.86.28          50000

Контроллеры репликаций

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

В определении replication.json:

В разделе podTemplate допускается использоваться те же самые настройки, что и в определении оболочки pod. В данном случае мы хотим, чтобы подчиненный сервер Jenkins автоматически подключался к нашему главному серверу, а не выполнялся поиск с просмотром большого числа адресов. Для этого мы выполняем команду jenkins-slave.sh с параметром -master, указывающим подчиненному серверу на главный сервер Jenkins, работающий в Kubernetes. Обратите внимание, что в определении сервиса Jenkins мы используем специальные переменные среды окружения Kubernetes (JENKINS_SERVICE_HOST и JENKINS_SERVICE_PORT). Команда image переопределена так, чтобы конфигурировать контейнер так, чтобы при повторном использовании существующих образов обращаться к переменным окружения, предоставляемым сервисом. Это также можно делать и в определениях оболочек pod.

Создание реплик с помощью kubecfg:

$ ./cluster/kubecfg.sh -c kubernetes-jenkins/replication.json create replicationControllers

Name                Image(s)                            Selector             Replicas
----------          ----------                          ----------           ----------
jenkins-slave       csanchez/jenkins-swarm-slave:1.21   name=jenkins-slave   1

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

$ ./cluster/kubecfg.sh list pods

Name                                   Image(s)                            Host                    Labels               Status
----------                             ----------                          ----------              ----------           ----------
jenkins                                csanchez/jenkins-swarm:1.565.3.3    10.245.2.3/10.245.2.3   name=jenkins         Running
07651754-4f88-11e4-b01e-0800279696e1   csanchez/jenkins-swarm-slave:1.21   10.245.2.2/10.245.2.2   name=jenkins-slave   Pending

При первом запуске образа jenkins-swarm-slave сервер minion должен загрузить образ из репозитория Docker, и через некоторое время, которое зависит от вашего интернет-соединения, подчиненные сервера должны автоматически подключиться к серверу Jenkins. Если перейти на сервер, с которого запущен подчиненный сервер, то с помощью команды docker ps можно увидеть работающий контейнер, а с помощью журналов docker можно разобраться с любой проблемой, возникшей при запуске контейнера.

Контроллер репликаций может автоматически перенастраиваться на любое другое количество необходимых реплик:

$ ./cluster/kubecfg.sh resize jenkins-slave 2

И снова обновляем оболочки pod для того, чтобы увидеть, где запущена каждая реплика.

$ ./cluster/kubecfg.sh list pods
Name                                   Image(s)                            Host                    Labels               Status
----------                             ----------                          ----------              ----------           ----------
07651754-4f88-11e4-b01e-0800279696e1   csanchez/jenkins-swarm-slave:1.21   10.245.2.2/10.245.2.2   name=jenkins-slave   Running
a22e0d59-4f88-11e4-b01e-0800279696e1   csanchez/jenkins-swarm-slave:1.21   10.245.2.3/10.245.2.3   name=jenkins-slave   Pending
jenkins                                csanchez/jenkins-swarm:1.565.3.3    10.245.2.3/10.245.2.3   name=jenkins         Running

Планирование

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

Самовосстановление

Одним из преимуществ использования Kubernetes является автоматизированное управление работой контейнеров и их восстановление.

Если по какой-нибудь причине, например, из-за того, некоторый процесс закончится аварийно, контейнер, работающий на сервере Jenkins, остановится, Kubernetes заметит это и через несколько секунд создаст новый контейнер

$ vagrant ssh minion-2 -c 'docker kill `docker ps | grep csanchez/jenkins-swarm: | sed -e "s/ .*//"`'
51ba3687f4ee


$ ./cluster/kubecfg.sh list pods
Name                                   Image(s)                            Host                    Labels               Status
----------                             ----------                          ----------              ----------           ----------
jenkins                                csanchez/jenkins-swarm:1.565.3.3    10.245.2.3/10.245.2.3   name=jenkins         Failed
07651754-4f88-11e4-b01e-0800279696e1   csanchez/jenkins-swarm-slave:1.21   10.245.2.2/10.245.2.2   name=jenkins-slave   Running
a22e0d59-4f88-11e4-b01e-0800279696e1   csanchez/jenkins-swarm-slave:1.21   10.245.2.3/10.245.2.3   name=jenkins-slave   Running

И спустя некоторое время, как правило, не более чем через минуту, мы увидим следующее

Name                                   Image(s)                            Host                    Labels               Status
----------                             ----------                          ----------              ----------           ----------
jenkins                                csanchez/jenkins-swarm:1.565.3.3    10.245.2.3/10.245.2.3   name=jenkins         Running
07651754-4f88-11e4-b01e-0800279696e1   csanchez/jenkins-swarm-slave:1.21   10.245.2.2/10.245.2.2   name=jenkins-slave   Running
a22e0d59-4f88-11e4-b01e-0800279696e1   csanchez/jenkins-swarm-slave:1.21   10.245.2.3/10.245.2.3   name=jenkins-slave   Running

Размещение каталога данных Jenkins внутри тома гарантирует, что данные останутся целыми даже после того, как контейнер прекратит существование, поскольку мы не потеряем никаких заданий Jenkins или созданных данных. И поскольку Kubernetes в каждом сервере minion использует прокси для подключения к сервисам, подчиненные серверы снова автоматически подключатся к новому серверу Jenkins независимо от того, где они выполняются! И тоже самое произойдет, если прекратит свое существование какой-либо из подчиненных контейнеров: система автоматически создаст новый контейнер и благодаря имеющемуся сервису он автоматически будет добавлен в пул серверов Jenkins.

Если случится что-нибудь более радикальное, например, остановится сервер, то в настоящее время Kubernetes еще не предложит возможность перенести контейнеры на другие сервера minion, он просто укажет, что в оболочках pod возникла ошибка (Failed).

$ vagrant halt minion-2
==> minion-2: Attempting graceful shutdown of VM...
$ ./cluster/kubecfg.sh list pods
Name                                   Image(s)                            Host                    Labels               Status
----------                             ----------                          ----------              ----------           ----------
jenkins                                csanchez/jenkins-swarm:1.565.3.3    10.245.2.3/10.245.2.3   name=jenkins         Failed
07651754-4f88-11e4-b01e-0800279696e1   csanchez/jenkins-swarm-slave:1.21   10.245.2.2/10.245.2.2   name=jenkins-slave   Running
a22e0d59-4f88-11e4-b01e-0800279696e1   csanchez/jenkins-swarm-slave:1.21   10.245.2.3/10.245.2.3   name=jenkins-slave   Failed

Вывод из эксплуатации

В kubecfg предоставляется несколько команд для остановки контроллеров репликаций, оболочек pod и сервисов и удаления их определений.

Чтобы остановить контроллер репликаций, задайте количество реплик равным значению 0 и в результате произойдет завершение работы всех контейнеров на подчиненных серверах Jenkins:

$ ./cluster/kubecfg.sh stop jenkins-slave

Чтобы его удалить:

$ ./cluster/kubecfg.sh rm jenkins-slave

Чтобы удалить оболочку pod сервера Jenkins (в результате будет завершена работа главного контейнера Jenkins):

$ ./cluster/kubecfg.sh delete pods/jenkins

Чтобы удалить сервис:

$ ./cluster/kubecfg.sh delete services/jenkins
$ ./cluster/kubecfg.sh delete services/jenkins-slave

Заключение

Проект Kubernetes все еще очень молодой, но весьма перспективный с точки зрения управления развертыванием Docker на нескольких серверах и упрощения выполнения долгоживущих и распределенных контейнеров Docker. Благодаря абстрагированию инфраструктурных концепций и работе с состояниями, а не с процессами, в нем сразу «из коробки» предоставляется более легковесный механизм работы кластеров, в том числе возможность самовосстанавления. Если кратко, то Kubernetes упрощает управление контейнерами Docker.