Плагіни томів для сховищ у Kubernetes: від Flexvolume до CSI

Плагіни томів для сховищ у Kubernetes: від Flexvolume до CSI

За часів, коли Kubernetes був ще v1.0.0, існували плагіни для томів (volume plugins). Потрібні вони були для підключення до Kubernetes систем зберігання персистентних (постійних) даних контейнерів. Кількість їх була невелика, а серед перших — такі провайдери сховищ, як GCE PD, Ceph, AWS EBS та інші.

Поставлялися плагіни разом із Kubernetes, за що й отримали свою назву – in-tree. Проте багатьом наявного набору таких плагінів виявилося недостатнім. Умільці додавали простенькі плагіни в ядро ​​Kubernetes за допомогою патчів, після чого збирали свій власний Kubernetes та ставили його на свої сервери. Але згодом розробники Kubernetes зрозуміли, що рибою проблему не вирішити. Людям потрібна вудка. І в релізі Kubernetes v1.2.0 вона з'явилася...

Плагін Flexvolume: вудка на мінімалках

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

Зупинимося і докладніше розглянемо, що є драйвер FlexVolume. Це якийсь виконуваний файл (Бінарний файл, Python-скрипт, Bash-скрипт і т.п.), який при виконанні приймає на вхід аргументи командного рядка і повертає повідомлення із заздалегідь відомими полями в JSON-форматі. Першим аргументом командного рядка за згодою завжди є метод, інші аргументи — його параметри.

Плагіни томів для сховищ у Kubernetes: від Flexvolume до CSI
Схема підключення CIFS Shares до OpenShift. Драйвер Flexvolume - прямо по центру

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

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

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

flexvolume_driver init # отвечает за инициализацию плагина
# Формат возвращаемого сообщения:
{
  "status": "Success"/"Failure"/"Not supported",
  "message": "По какой причине был возвращен именно такой статус",
  // Определяет, использует ли драйвер методы attach/deatach
  "capabilities":{"attach": True/False}
}

Використання методів 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-драйвер.

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

Ось приклад такого 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

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

Плагіни томів для сховищ у Kubernetes: від Flexvolume до CSI
Схема роботи з Ceph в операторі Rook: драйвер Flexvolume на схемі знаходиться всередині агента Rook

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

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

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

До побачення, наші лагідні… плагіни in-tree!

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

Виявляється, щоб використовувати нову версію плагіна для сховища, потрібно оновити весь кластер. На додаток до цього ви можете здивуватися, що нова версія Kubernetes раптом стане несумісною з ядром Linux… А тому витираєте сльози і скрипучи зубами погоджуєте з начальством і користувачами час оновлення ядра Linux і кластера Kubernetes. З можливим простоєм у наданні послуг.

Ситуація більш ніж комічна, не знаходите? Усьому співтоваристві стало зрозуміло, що підхід не працює. Розробники Kubernetes вольовим рішенням оголошують, що нові плагіни для роботи зі сховищами більше не прийматимуться в ядро. До того ж, як ми вже знаємо, у реалізації Flexvolume-плагіном було виявлено низку недоробок…

Раз і назавжди закрити питання з персистентними сховищами даних був покликаний останній доданий плагін для томів Kubernetes - CSI. Його альфа-версію, більш повно звану як Out-of-Tree CSI Volume Plugins, анонсували у релізі Кубернети 1.9.

Container Storage Interface, або спінінг CSI 3000!

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

Яким є пристрій CSI-плагіна в Kubernetes? CSI-плагін працює зі спеціальними драйверами (CSI-драйверами), написаними сторонніми розробниками. CSI-драйвер у Kubernetes мінімально повинен складатися з двох компонентів (pod'ів):

  • контролер - Керує зовнішніми персистентними сховищами. Релізується у вигляді gRPC-сервера, для якого використовується примітив StatefulSet.
  • вузол - Відповідає за монтування персистентних сховищ до вузлів кластера. Теж реалізується у вигляді gRPC-сервера, але для нього використовується примітив DaemonSet.

Плагіни томів для сховищ у Kubernetes: від Flexvolume до CSI
Схема роботи 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.

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

Ми вже маємо досвід використання ceph-csi, vsphere-csi і готові поповнювати цей список! Поки що CSI із покладеними на нього завданнями справляється на ура, а там поживемо-побачимо.

Не забувайте, що все нове це добре переосмислене старе!

PS

Читайте також у нашому блозі:

Джерело: habr.com

Додати коментар або відгук