Статьи

ZooKeeper на Кубернетес

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

Одна из первых вещей, которые я хотел попробовать, — это запустить ансамбль Apache ZooKeeper в kubernetes, и я подумал, что было бы неплохо поделиться этим опытом.

Для своих экспериментов я использовал Docker v. 1.3.0 и Openshift V3 , который я создал из исходного кода и включает в себя Kubernetes.

ZooKeeper на Docker

Управление ансамблем ZooKeeper определенно не тривиальная задача. Обычно вам нужно настроить нечетное количество серверов, и все серверы должны быть осведомлены друг о друге. Это сам по себе PITA, но он становится еще более болезненным, когда вы работаете с чем-то столь же статичным, как образы докера. Основная трудность может быть выражена как:

« Как вы можете создать несколько контейнеров из одного изображения и заставить их указывать друг на друга? »

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

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

1
docker run -p 2181:2181 -v /path/to/my/conf:/opt/zookeeper/conf my/zookeeper

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

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

И последнее, но не менее важное: можно объединить два подхода в один и сделать что-то вроде:

  • Сделайте возможным предоставление базовой конфигурации извне, используя тома.
  • Используйте env и scripting, чтобы просто настроить ансамбль.

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

Создание собственного изображения для ZooKeeper

Я просто сосредоточусь на конфигурации, которая требуется для ансамбля. Чтобы настроить ансамбль ZooKeeper, для каждого сервера необходимо назначить числовой идентификатор, а затем добавить в свою конфигурацию запись для каждого сервера zookeeper, которая содержит IP-адрес сервера, одноранговый порт сервера и порт выбора.

Идентификатор сервера добавляется в файл myid в dataDir. Остальная часть конфигурации выглядит так:

1
2
3
4
5
server.1=server1.example.com:2888:3888
server.2=server2.example.com:2888:3888
server.3=server3.example.com:2888:3888
...
server.current=[bind address]:[peer binding port]:[election biding port]

Обратите внимание, что если идентификатор сервера равен X, запись server.X должна содержать ip и порты связывания, а не ip и порты соединения.

Итак, что нам действительно нужно передать контейнеру в качестве переменных среды, так это:

  1. Идентификатор сервера.
  2. Для каждого сервера в ансамбле:
    1. Имя хоста или ip
    2. Одноранговый порт
    3. Порт выборов

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

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
if [ ! -z "$SERVER_ID" ]; then
  echo "$SERVER_ID" > /opt/zookeeper/data/myid
  #Find the servers exposed in env.
  for i in `echo {1..15}`;do
 
    HOST=`envValue ZK_PEER_${i}_SERVICE_HOST`
    PEER=`envValue ZK_PEER_${i}_SERVICE_PORT`
    ELECTION=`envValue ZK_ELECTION_${i}_SERVICE_PORT`
 
    if [ "$SERVER_ID" = "$i" ];then
      echo "server.$i=0.0.0.0:2888:3888" >> conf/zoo.cfg
    elif [ -z "$HOST" ] || [ -z "$PEER" ] || [ -z "$ELECTION" ] ; then
      #if a server is not fully defined stop the loop here.
      break
    else
      echo "server.$i=$HOST:$PEER:$ELECTION" >> conf/zoo.cfg
    fi
 
  done
fi

Для простоты функция чтения ключей и значений из env исключена.

Полный образ и сценарии помощи для запуска ансамблей zookeeper переменных размеров можно найти в хранилище fabric8io .

ZooKeeper на Кубернетес

Приведенный выше образ докера можно использовать непосредственно с докером, если вы позаботитесь о переменных среды. Теперь я собираюсь описать, как это изображение может быть использовано с kubernetes. Но сначала немного бессвязно…

Что мне действительно нравится в использовании kubernetes с ZooKeeper, так это то, что kubernetes воссоздает контейнер, если он умирает или проверка состояния не удалась. Для ZooKeeper это также означает, что если контейнер, на котором размещен сервер ансамбля, умрет, он будет заменен новым. Это гарантирует, что постоянно будет кворум серверов ZooKeeper.

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

Создание конферта Kubernetes для ZooKeeper

Я постараюсь объяснить, как вы можете создать 3 серверных ансамбля ZooKeeper в Кубернетесе.

Нам нужны 3 контейнера для докеров, все они работают под управлением ZooKeeper с правильными переменными среды:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
{
                                    "image": "fabric8/zookeeper",
                                    "name": "zookeeper-server-1",
                                    "env": [
                                        {
                                            "name": "ZK_SERVER_ID",
                                            "value": "1"
                                        }
                                    ],
                                    "ports": [
                                        {
                                            "name": "zookeeper-client-port",
                                            "containerPort": 2181,
                                            "protocol": "TCP"
                                        },
                                        {
                                            "name": "zookeeper-peer-port",
                                            "containerPort": 2888,
                                            "protocol": "TCP"
                                        },
                                        {
                                            "name": "zookeeper-election-port",
                                            "containerPort": 3888,
                                            "protocol": "TCP"
                                        }
                                    ]
                                }

ENV необходимо указать все параметры, обсужденные ранее.

Итак, нам нужно добавить вместе с ZK_SERVER_ID следующее:

  • ZK_PEER_1_SERVICE_HOST
  • ZK_PEER_1_SERVICE_PORT
  • ZK_ELECTION_1_SERVICE_PORT
  • ZK_PEER_2_SERVICE_HOST
  • ZK_PEER_2_SERVICE_PORT
  • ZK_ELECTION_2_SERVICE_PORT
  • ZK_PEER_3_SERVICE_HOST
  • ZK_PEER_3_SERVICE_PORT
  • ZK_ELECTION_3_SERVICE_PORT

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

Итак, как мы настраиваем эти сервисы?

Для их настройки нам нужно знать:

  • название порта
  • КУБЕРНЕТЕС ПОД ПРЕДЛОЖЕНИЕ

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

1
2
3
4
"labels": {
    "name": "zookeeper-pod",
    "server"1
}

Нечто подобное может работать. Теперь мы готовы определить сервис. Я просто покажу, как мы можем выставить одноранговый порт сервера с идентификатором 1 как сервис. Остальное можно сделать аналогичным образом:

01
02
03
04
05
06
07
08
09
10
11
12
{
    "apiVersion": "v1beta1",
    "creationTimestamp": null,
    "id": "zk-peer-1",
    "kind": "Service",
    "port": 2888,
    "containerPort": "zookeeper-peer-port",
    "selector": {
        "name": "zookeeper-pod",
        "server": 1
    }
}

Основная идея заключается в том, что в определении службы вы создаете селектор, который можно использовать для запроса / фильтрации модулей. Затем вы определяете имя порта для экспозиции, и это почти все. Просто чтобы уточнить, нам нужно определение сервиса, подобное приведенному выше для каждого контейнера сервера zookeeper. И, конечно, мы должны сделать то же самое для избирательного порта.

Наконец, мы можем определить другой вид сервиса для порта подключения клиента. На этот раз мы не будем указывать идентификатор сервера в селекторе, что означает, что будут выбраны все 3 сервера. В этом случае kubernetes будет балансировать нагрузку на всех серверах ZooKeeper. Поскольку ZooKeeper предоставляет единый образ системы (не имеет значения, к какому серверу вы подключены), это довольно удобно.

01
02
03
04
05
06
07
08
09
10
11
12
{
    "apiVersion": "v1beta1",
    "creationTimestamp": null,
    "id": "zk-client",
    "kind": "Service",
    "port": 2181,
    "createExternalLoadBalancer": "true",
    "containerPort": "zookeeper-client-port",
    "selector": {
        "name": "zookeeper-pod"
    }
}

Я надеюсь, что вы сочли полезным. Определенно есть место для улучшения, поэтому не стесняйтесь оставлять комментарии.