Хабрахабр

Плагины томов для хранилищ в Kubernetes: от Flexvolume к CSI

0. Во времена, когда Kubernetes был ещё v1. Нужны они были для подключения к Kubernetes систем для хранения персистентных (постоянных) данных контейнеров. 0, существовали плагины для томов (volume plugins). Количество их было невелико, а в числе первых — такие провайдеры хранилищ, как GCE PD, Ceph, AWS EBS и другие.

Однако многим существующего набора таких плагинов оказалось недостаточным. Поставлялись плагины вместе с Kubernetes, за что и получили своё название — in-tree. Но со временем разработчики Kubernetes поняли, что рыбой проблему не решить. Умельцы добавляли простенькие плагины в ядро Kubernetes при помощи патчей, после чего собирали свой собственный Kubernetes и ставили его на свои серверы. И в релизе Kubernetes v1. Людям нужна удочка. 0 она появилась… 2.

Плагин Flexvolume: удочка на минималках

Разработчиками Kubernetes был создан плагин FlexVolume, который являлся логической обвязкой из переменных и методов для работы с реализуемыми сторонними разработчиками Flexvolume-драйверами.

Это некий исполняемый файл (бинарный файл, Python-скрипт, Bash-скрипт и т.п.), который при выполнении принимает на вход аргументы командной строки и возвращает сообщение с заранее известными полями в JSON-формате. Остановимся и подробнее рассмотрим, что представляет собой драйвер FlexVolume. Первым аргументом командной строки по соглашению всегда является метод, а остальные аргументы — его параметры.

Драйвер Flexvolume — прямо по центру
Схема подключения CIFS Shares в OpenShift.

Минимальный набор методов выглядит так:

flexvolume_driver mount # отвечает за присоединение тома к pod'у
# Формат возвращаемого сообщения:
{ "status": "Success"/"Failure"/"Not supported", "message": "По какой причине был возвращен именно такой статус",
} flexvolume_driver unmount # отвечает за отсоединение тома от pod'а
# Формат возвращаемого сообщения:
{ "status": "Success"/"Failure"/"Not supported", "message": "По какой причине был возвращен именно такой статус",
} flexvolume_driver init # отвечает за инициализацию плагина
# Формат возвращаемого сообщения:

}

Использование методов attach и detach определит сценарий, по которому в будущем kubelet будет действовать при вызове драйвера. Также существуют специальные методы expandvolume и expandfs, которые отвечают за динамическое изменение размера тома.

В качестве примера изменений, которые добавляет метод expandvolume, а вместе с ним — и возможность выполнять изменение размера томов в реальном времени, можно ознакомиться с нашим pull request'ом в Rook Ceph Operator.

А вот пример реализации Flexvolume-драйвера для работы с NFS:

usage() { err "Invalid usage. Usage: " err "\t$0 init" err "\t$0 mount <mount dir> <json params>" err "\t$0 unmount <mount dir>" exit 1
} err() { echo -ne $* 1>&2
} log() { echo -ne $* >&1
} ismounted() { MOUNT=`findmnt -n ${MNTPATH} 2>/dev/null | cut -d' ' -f1` if [ "${MOUNT}" == "${MNTPATH}" ]; then echo "1" else echo "0" fi
} domount() { MNTPATH=$1 NFS_SERVER=$(echo $2 | jq -r '.server') SHARE=$(echo $2 | jq -r '.share') if [ $(ismounted) -eq 1 ] ; then log '{"status": "Success"}' exit 0 fi mkdir -p ${MNTPATH} &> /dev/null mount -t nfs ${NFS_SERVER}:/${SHARE} ${MNTPATH} &> /dev/null if [ $? -ne 0 ]; then err "{ \"status\": \"Failure\", \"message\": \"Failed to mount ${NFS_SERVER}:${SHARE} at ${MNTPATH}\"}" exit 1 fi log '{"status": "Success"}' exit 0
} unmount() { MNTPATH=$1 if [ $(ismounted) -eq 0 ] ; then log '{"status": "Success"}' exit 0 fi umount ${MNTPATH} &> /dev/null if [ $? -ne 0 ]; then err "{ \"status\": \"Failed\", \"message\": \"Failed to unmount volume at ${MNTPATH}\"}" exit 1 fi log '{"status": "Success"}' exit 0
} op=$1 if [ "$op" = "init" ]; then log '{"status": "Success", "capabilities": {"attach": false}}' exit 0
fi if [ $# -lt 2 ]; then usage
fi shift case "$op" in mount) domount $* ;; unmount) unmount $* ;; *) log '{"status": "Not supported"}' exit 0
esac exit 1

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

/usr/libexec/kubernetes/kubelet-plugins/volume/exec/имя_поставщика_хранилища~имя_драйвера/

… но при использовании различных дистрибутивов Kubernetes (OpenShift, Rancher…) путь может быть другим.

Проблемы Flexvolume: как правильно закидывать удочку?

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

При появлении нового узла в кластере на нем автоматически оказывается pod из нашего DaemonSet'a, к которому присоединяется локальный том по пути для нахождения Flexvolume-драйверов. Решением данной проблемы послужил один из примитивов Kubernetes — DaemonSet. При успешном создании pod копирует необходимые файлы для работы драйвера на диск.

Вот пример такого DaemonSet'а для выкладывания Flexvolume-плагина:

apiVersion: extensions/v1beta1
kind: DaemonSet
metadata: name: flex-set
spec: template: metadata: name: flex-deploy labels: app: flex-deploy spec: containers: - image: <deployment_image> name: flex-deploy securityContext: privileged: true volumeMounts: - mountPath: /flexmnt name: flexvolume-mount volumes: - name: flexvolume-mount hostPath: path: <host_driver_directory>

… и пример Bash-скрипта для выкладывания Flexvolume-драйвера:

#!/bin/sh set -o errexit
set -o pipefail VENDOR=k8s.io
DRIVER=nfs driver_dir=$VENDOR${VENDOR:+"~"}${DRIVER}
if [ ! -d "/flexmnt/$driver_dir" ]; then mkdir "/flexmnt/$driver_dir"
fi cp "/$DRIVER" "/flexmnt/$driver_dir/.$DRIVER"
mv -f "/flexmnt/$driver_dir/.$DRIVER" "/flexmnt/$driver_dir/$DRIVER" while : ; do sleep 3600
done

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


Схема работы с Ceph в операторе Rook: драйвер Flexvolume на схеме находится внутри агента Rook

Изначально плагин Flexvolume не был задуман для реализации настолько сложных систем. Следующей проблемой при использовании Flexvolume-драйверов является то, что для большинства хранилищ на узле кластера должен быть установлен необходимый для этого софт (например, пакет ceph-common для Ceph).

Оригинальное решение для этой проблемы можно увидеть в реализации Flexvolume-драйвера оператора Rook:

IPC-сокет для общения лежит в том же каталоге, что и сам драйвер. Сам драйвер выполнен в виде RPC-клиента. После копирования необходимых файлов драйвера rook этот pod не умирает, а подключается к IPC-сокету через присоединенный том как полноценный RPC-сервер. Мы с вами помним, что для копирования файлов драйвера хорошо бы использовать DaemonSet, который в качестве тома подключает себе директорию с драйвером. IPC-сокет дает уверенность, что kubelet будет общаться именно с тем pod'ом, который находится с ним на одном узле. Пакет ceph-common уже установлен внутри контейнера pod’а. Всё гениальное просто!..

До свидания, наши ласковые… плагины in-tree!

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

В дополнение к этому вы можете удивиться, что новая версия Kubernetes вдруг станет несовместимой с используемым ядром Linux… А посему вы вытираете слезы и скрипя зубами согласовываете с начальством и пользователями время обновления ядра Linux и кластера Kubernetes. Оказывается, чтобы использовать новую версию плагина для хранилища, нужно обновить весь кластер. С возможным простоем в предоставлении услуг.

Всему сообществу стало ясно, что подход не работает. Ситуация более чем комичная, не находите? Ко всему прочему, как мы уже знаем, в реализации Flexvolume-плагином был выявлен ряд недоработок… Волевым решением разработчики Kubernetes объявляют, что новые плагины для работы с хранилищами более не будут приниматься в ядро.

Его альфа-версию, более полно называемую как Out-of-Tree CSI Volume Plugins, анонсировали в релизе Kubernetes 1. Раз и навсегда закрыть вопрос с персистентными хранилищами данных был призван последний добавленный плагин для томов в Kubernetes — CSI. 9.

Container Storage Interface, или спиннинг CSI 3000!

Первым делом хотелось бы отметить, что CSI — это не просто volume plugin, а самый настоящий стандарт по созданию пользовательских компонентов для работы с хранилищами данных. Предполагалось, что системы оркестрации контейнерами, такие как Kubernetes и Mesos, должны «научиться» работе с компонентами, реализованными по этому стандарту. И вот Kubernetes уже научился.

CSI-плагин работает со специальными драйверами (CSI-драйверами), написанными сторонними разработчиками. Каково же устройство CSI-плагина в Kubernetes? CSI-драйвер в Kubernetes минимально должен состоять из двух компонентов (pod’ов):

  • Controller — управляет внешними персистентными хранилищами. Релизуется в виде gRPC-сервера, для которого используется примитив StatefulSet.
  • Node — отвечает за монтирования персистентных хранилищ к узлам кластера. Тоже реализуется в виде gRPC-сервера, но для него используется примитив DaemonSet.


Схема работы CSI-плагина в Kubernetes

О некоторых других подробностях работы CSI вы можете узнать, например, из статьи «Understanding the CSI», перевод которой мы публиковали год назад.

Плюсы такой реализации

  • Для базовых вещей — например, для регистрации драйвера для узла — разработчики Kubernetes реализовали набор контейнеров. Больше не нужно самим формировать JSON-ответ с capabilities, как это делалось для плагина Flexvolume.
  • Вместо «подсовывания» на узлы исполняемых файлов мы теперь выкладываем в кластер pod’ы. Этого мы изначально и ждем от Kubernetes: все процессы происходят внутри контейнеров, развернутых при помощи примитивов Kubernetes.
  • Для реализации сложных драйверов больше не нужно разрабатывать RPC-сервер и RPC-клиент. Клиент за нас реализовали разработчики Kubernetes.
  • Передача аргументов для работы по протоколу gRPC гораздо удобнее, гибче и надежнее, чем их передача через аргументы командной строки. Для понимания, как добавить в CSI поддержку метрик по использованию тома при помощи добавления стандартизированного gRPC-метода, можно ознакомиться с нашим pull request'ом для драйвера vsphere-csi.
  • Общение происходит через IPC-сокеты, чтобы не путаться, тому ли pod'у kubelet отправил запрос.

Этот список вам ничего не напоминает? Преимущества CSI — это решение тех самых проблем, что не были учтены при разработке плагина Flexvolume.

Выводы

CSI как стандарт реализации пользовательских плагинов для взаимодействия с хранилищами данных был принят сообществом очень тепло. Более того, благодаря своим преимуществам и универсальности, CSI-драйверы создаются даже для таких хранилищ, как Ceph или AWS EBS, плагины для работы с которыми были добавлены ещё в самой первой версии Kubernetes.

Планируется продолжать поддержку плагина Flexvolume, но разработки новых функциональных возможностей для него не будет. В начале 2019 года плагины in-tree были объявлены устаревшими.

Пока что CSI с возложенными на него задачами справляется на ура, а там поживем-увидим. Сами мы уже имеем опыт использования ceph-csi, vsphere-csi и готовы пополнять этот список!

Не забывайте, что всё новое — это хорошо переосмысленное старое!

P.S.

Читайте также в нашем блоге:

Теги
Показать больше

Похожие статьи

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *

Кнопка «Наверх»
Закрыть