Автор статьи: Рустем Галиев
IBM Senior DevOps Engineer & Integration Architect
Привет Хабр!
В Kubernetes StatefulSet
— это реплицированные группы Pod’ов, аналогичные ReplicaSet’ам.
Каждая реплика получает постоянное имя хоста с уникальным индексом (например, database-0
, database-1
и т. д.).
Каждая реплика создается в порядке от самого низкого до самого высокого индекса и создание блокируется до тех пор, пока под с предыдущим индексом не станет работоспособным и доступным. Это относится и к масштабированию.
При удалении StatefulSet
каждый из управляемых подов реплики также удаляется в порядке убывания. Это также относится к уменьшению количества реплик.
Оказывается, этот простой набор требований значительно упрощает развертывание приложений для хранения данных в Kubernetes. Например, сочетание стабильных имен хостов (например, database-0
) и ограничений порядка означает, что все реплики, кроме первой, могут надежно ссылаться на database-0
для целей обнаружения и установления кворума репликации.
Сегодня мы развернем реплицированный кластер MongoDB с StatefulSet
.
Для начала создадим реплицированный набор из трех модулей MongoDB, используя объект StatefulSet
. Основной контейнер приложения использует образ контейнера mongo:3.4.24
и запускает процесс mongod
. Когда вы запускаете mongod
, запускается процесс MongoDB и запускается он в фоновом режиме. У процесса есть несколько параметров по умолчанию, например, сохранение данных в /data/db
и запуск через порт 27017.
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: mongo
spec:
serviceName: "mongo"
replicas: 3
selector:
matchLabels:
app: mongo
template:
metadata:
labels:
app: mongo
spec:
containers:
- name: mongodb
image: mongo:3.4.24
command:
- mongod
- --replSet
- rs0
ports:
- containerPort: 27017
name: peer
Пока не создавайте объект, так как мы будем вносить дополнительные изменения в манифест YAML.
Инициализируем кластер MongoDB с помощью init Container
Чтобы автоматизировать развертывание нашего кластера MongoDB на основе StatefulSet, мы собираемся добавить дополнительный контейнер в поды для выполнения инициализации.
Чтобы настроить этот модуль без создания нового образа Docker, мы собираемся использовать ConfigMap
для добавления скрипта в существующий образ MongoDB.
Мы собираемся запустить этот скрипт, используя контейнер инициализации. Контейнеры инициализации (или init Container) — это специализированные контейнеры, которые запускаются один раз при запуске пода. Они обычно используются в таких случаях, когда есть небольшой объем работы по настройке, которую полезно выполнить до запуска основного приложения. В определении пода есть отдельный список initContainers
, в котором можно определить контейнеры инициализации.
Последним этапом производства нашего кластера MongoDB является добавление проверок жизнеспособности в наши контейнеры, обслуживающие Mongo:
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: mongo
spec:
serviceName: "mongo"
replicas: 3
selector:
matchLabels:
app: mongo
template:
metadata:
labels:
app: mongo
spec:
containers:
- name: mongodb
image: mongo:3.4.24
command:
- mongod
- --replSet
- rs0
ports:
- containerPort: 27017
name: web
livenessProbe:
exec:
command:
- /usr/bin/mongo
- --eval
- db.serverStatus()
initialDelaySeconds: 10
timeoutSeconds: 10
# This container initializes the mongodb server, then sleeps.
- name: init-mongo
image: mongo:3.4.24
command:
- bash
- /config/init.sh
volumeMounts:
- name: config
mountPath: /config
volumes:
- name: config
configMap:
name: "mongo-init"
Замените содержимое в файле mongo.yaml
. На следующем шаге мы создадим ConfigMap
с именем mongo-init
.
Создание карты конфигурации
Обратите внимание, что Pod монтирует ConfigMap Volume (том) с именем mongo-init
. Этот ConfigMap содержит скрипт, который выполняет нашу инициализацию. Во-первых, скрипт определяет, работает ли он на mongo-0
. Если он находится на mongo-0
, он создает ReplicaSet
с помощью той же команды, которую мы ранее запускали императивно. Если он находится в другой реплике Mongo,ждет, пока ReplicaSet exists
(как бы правильно сказать, не появится), а затем регистрируется как мембер этого ReplicaSet
.
Следующий манифест YAML содержит полный объект ConfigMap
:
apiVersion: v1
kind: ConfigMap
metadata:
name: mongo-init
data:
init.sh: |
#!/bin/bash
# Need to wait for the readiness health check to pass so that the
# mongo names resolve. This is kind of wonky.
until ping -c 1 ${HOSTNAME}.mongo; do
echo "waiting for DNS (${HOSTNAME}.mongo)..."
sleep 2
done
until /usr/bin/mongo --eval 'printjson(db.serverStatus())'; do
echo "connecting to local mongo..."
sleep 2
done
echo "connected to local."
HOST=mongo-0.mongo:27017
until /usr/bin/mongo --host=${HOST} --eval 'printjson(db.serverStatus())'; do
echo "connecting to remote mongo..."
sleep 2
done
echo "connected to remote."
if [[ "${HOSTNAME}" != 'mongo-0' ]]; then
until /usr/bin/mongo --host=${HOST} --eval="printjson(rs.status())" \
| grep -v "no replset config has been received"; do
echo "waiting for replication set initialization"
sleep 2
done
echo "adding self to mongo-0"
/usr/bin/mongo --host=${HOST} \
--eval="printjson(rs.add('${HOSTNAME}.mongo'))"
fi
if [[ "${HOSTNAME}" == 'mongo-0' ]]; then
echo "initializing replica set"
/usr/bin/mongo --eval="printjson(rs.initiate(\
{'_id': 'rs0', 'members': [{'_id': 0, \
'host': 'mongo-0.mongo:27017'}]}))"
fi
echo "initialized"
Вы заметите, что скрипт, определенный ConfigMap
, немедленно завершает работу. Это важно при использовании initContainers
. Каждый контейнер инициализации, прежде чем запускаться, ждет пока предыдущий контейнер не будет выполнен. Основной контейнер приложения ожидает завершения всех контейнеров инициализации. Если бы этот скрипт не завершился, основной сервер монго никогда бы не запустился.
Сначала создайте объект ConfigMap
:kubectl apply -f mongo-configmap.yaml
Затем мы создадим объект StatefulSet
, который теперь сможет ссылаться на ConfigMap
по имени.kubectl apply -f mongo.yaml
После создания различия между ReplicaSet
и StatefulSet
становятся очевидными. Учитывая, что для создания подов StatefulSet
потребуется некоторое время, вам придется повторно запускать одну и ту же команду несколько раз, пока не будет достигнуто количество трех реплик:kubectl get pods
Между этим и тем, что вы увидите с ReplicaSet
, есть два важных отличия. Во-первых, каждый реплицированный Pod имеет числовой индекс (0, 1,…) вместо случайного суффикса, который добавляется контроллером ReplicaSet
. Во-вторых, поды медленно создаются по порядку, а не все сразу, как это было бы с ReplicaSet
.
Предоставление MongoDB как сервиса
После создания StatefulSet
нам также необходимо создать «безголовую» службу для управления записями DNS для StatefulSet
. В Kubernetes сервис называется «безголовым», если у него нет виртуального IP-адреса кластера. Поскольку со StatefulSets
каждый Pod имеет уникальный идентификатор, на самом деле не имеет смысла иметь IP-адрес для балансировки нагрузки для реплицируемого сервиса. Вы можете создать безголовый сервис, используя clusterIP:
None в спецификации сервиса:
apiVersion: v1
kind: Service
metadata:
name: mongo
spec:
ports:
- port: 27017
name: peer
clusterIP: None
selector:
app: mongo
После создания этой службы обычно заполняются четыре записи DNS. Как обычно, создается mongo.default.svc.cluster.local
, но, в отличие от стандартной службы, поиск DNS по этому имени хоста предоставляет все адреса в StatefulSet
. Кроме того, создаются записи для mongo-0.mongo.default.svc.cluster.local
, а также для mongo-1.mongo
и mongo-2.mongo
. Каждый из них разрешается в определенный IP-адрес индекса реплики в StatefulSet
. Таким образом, с помощью StatefulSets
вы получаете четко определенные постоянные имена для каждой реплики в наборе. Это часто бывает очень полезно при настройке решения для хранения данных с репликацией.
Создайте службу с помощью следующей команды:kubectl apply -f mongo-service.yaml
После того, как мы объединили StatefulSets
, pvc
и проверку живучести, у нас есть защищенная, масштабируемая облачная установка MongoDB, работающая в Kubernetes. Хотя в этой теме речь шла о MongoDB, шаги по созданию StatefulSet
для управления другими решениями для хранения очень похожи и можно следовать аналогичным шаблонам.
В завершение хочу пригласить вас на бесплатный урок, где мои коллеги из OTUS расскажут как устроен мониторинг кластера, его компоненты и приложения в кластере. Вы изучите различные подходы мониторинга, подходы к мониторингу как приложения так и компонентов кластера, основные метрики Kubernetes. Также узнаете про кластеризацию/федерацию Prometheus, дополнительные хранилища метрик для prometheus (victoria metrics; thanos, cortex).