Руководство: как использовать S3-хранилище в Kubernetes - Академия Selectel

Руководство: как использовать S3-хранилище в Kubernetes

Собираем тестовый стенд из кластера Managed Kubernetes и показываем, как установить и настроить нужные компоненты через Terraform.

Изображение записи

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

Меня зовут Филипп Федоров, я DevOps-инженер в Selectel. В своей обзорной статье хочу разобрать, какие есть решения для работы с объектным хранилищем из K8s. Соберу тестовый стенд из кластера Managed Kubernetes и покажу, как установить и настроить нужные компоненты через Terraform. Начинаем!

Знакомство с CSI: что это и когда использовать

Container Storage Interface (CSI) — это стандартный интерфейс, который позволяет Kubernetes взаимодействовать с различными системами хранения данных. Он унифицирует доступ к хранилищам данных и упрощает интеграцию различных систем хранения в кластере. Тем самым — обеспечивает гибкость и масштабируемость.

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

Кроме того, CSI-драйверы развиваются независимо от ядра Kubernetes и работают в пользовательском пространстве. Это значит, что они не требуют изменений в коде K8s для добавления новых функций или исправлений. Такой подход снижает риски безопасности, так как уязвимости в CSI-драйверах напрямую не влияют на Kubernetes. А еще это позволяет быстро реагировать на изменения в требованиях к хранению данных, развивать и обновлять драйверы отдельно от основного кода K8s.

Из чего состоит CSI

CSI можно разделить на компоненты, которые разрабатывают Kubernetes и сторонние поставщики хранилищ.

Компоненты от Kubernetes

  • Driver registrar — регистрирует CSI-драйверы в kubelet.
  • External provisioner — обрабатывает запросы на создание и удаление томов.
  • External attacher — управляет монтированием и размонтированием томов.

Компоненты от сторонних поставщиков

  • CSI Identity — проверяет и возвращает информацию о плагине.
  • CSI Controller — управляет жизненным циклом томов.
  • CSI Node — управляет монтированием и размонтированием томов на узлах.

Но как работает CSI на практике? Подготовим инфраструктуру и протестируем подключение к S3 из Kubernetes.

Подготовка инфраструктуры

До настройки и использования CSI необходимо подготовить инфраструктуру, которая будет включать кластер Kubernetes и S3-хранилище. В качестве основного IaC-инструмента я выбрал Terraform. Именно с помощью него буду создавать инфраструктуру и устанавливать Helm-чарты в кластер.

Первым делом нужно создать сервисного пользователя в панели управления Selectel. И установить роли «Администратор аккаунта» и «Администратор пользователей». О том, как это сделать, мы подробно рассказали в документации. Этот этап нужен для инициализации Terraform-провайдера Selectel, в который далее передадим логин и пароль.

Чтобы не писать самостоятельно большие портянки с кодом Terraform, воспользуемся кодом из публичного репозитория GitHub. Клонируем его и вносим некоторые коррективы. Далее пошагово опишу, что нужно будет сделать.

1. Клонируйте репозиторий к себе и перейдите в созданную папку:


    #: git clone git@github.com:selectel/selectel-infra-examples.git && cd selectel-infra-examples

2. В main.tf закомментируйте все модули и ресурсы, кроме project-with-user, mks и s3-credentials.

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

4. В качестве версии Managed Kubernetes укажите доступную версию. По завершении должен получиться главный файл main.tf следущего вида:


    # Создаем проект с пользователем
module "project-with-user" {
  source          = "./modules/os_project_with_user"
  os_project_name = "csi_test_project"      # Имя проекта
  os_username     = "csi_test_project_user" # Имя для нового проектного сервисного пользователя
}

# Создаем S3-ключ для пользователя
module "s3-creds" {
  source           = "./modules/s3/s3-credentials"
  os_user_id       = module.project-with-user.user_id
  os_project_id    = module.project-with-user.project_id
  credentials_name = "csi-s3-creds"

  # Важный блок, без него данный модуль пойдет вперед создания
  # проекта и пользователя и упадет с ошибкой!
  depends_on = [
    module.project-with-user
  ]
}

# Создаем MKs
module "mks" {
  source = "./modules/mks/k8s-cluster-standalone"

  cluster_name = "csi-test-cluster" # Имя кластера
  kube_version = "1.28.9"           # Версия кластера

  os_availability_zone = "ru-9a" # Регион и зона
  os_region            = "ru-9"  # Регион
  os_project_id        = module.project-with-user.project_id

  # Для теста оставьте как есть
  nodegroups     = 1
  ng_nodes_count = [2]
  ng_cpus        = [4]
  ng_ram_mb      = [8192]
  ng_volume_gb   = [100]
  ng_volume_type = ["fast"]
  ng_labels      = [{ "role" : "system" }]

  # Для того чтобы отключить gpu группы нод,
  # необходимо передать gpu_nodegroups = 0
  gpu_nodegroups     = 0
  gpu_ng_nodes_count = [0]
  gpu_ng_volume_gb   = [100]
  gpu_ng_volume_type = ["fast"]
  gpu_ng_labels      = [{ "role" : "gpu" }]
  gpu_ng_flavor      = ["3031"]

  nat_subnet_cidr   = "10.222.0.0/16"
  enable_autorepair = false
  network_id        = ""

  # Важный блок, без него данный модуль пойдет вперед создания
  # проекта и пользователя и упадет с ошибкой!
  depends_on = [
    module.project-with-user
  ]
}

5. Немного модифицируем код Terraform, чтобы мы могли устанавливать компоненты (по сути, Helm-чарты), используя Terraform-провайдер Helm. Добавим в файл providers.tf инициализацию helm-tf-провайдера:


    provider "helm" {
  kubernetes {
    host                   = module.mks.kube_config.server
    client_certificate     = base64decode(module.mks.kube_config.client_cert)
    client_key             = base64decode(module.mks.kube_config.client_key)
    cluster_ca_certificate = base64decode(module.mks.kube_config.cluster_ca_cert)
  }
}

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

6. Далее проверим конфигурацию — для этого выполним пару Terraform-команд. Инициализируем конфигурацию и проверим, какие изменения Terraform планирует внести в инфраструктуру:


    #: terraform init
#: terraform plan

В конце вывода команды terraform plan вы должны увидеть, что будет создано n ресурсов:


    ...
Plan: <n кол-во> to add, 0 to change, 0 to destroy.

7. Теперь применим конфигурацию и подтвердим изменения:


    #: terraform apply

В результате Terraform развернет все необходимое для тестового стенда: проект, сервисного пользователя, S3-контейнер, S3-ключ, кластер и ноды MKs и сеть для MKs.

Для выполнения команды kubectl и, например, доступа к кластеру через Lens, понадобится kube_config файл, который можно получать через панель управления. По готовности можно перейти дальше и начать уже знакомиться с CSI-драйверами для S3. Как и отмечал ранее, рекомендую устанавливать чарты с помощью terraform provider helm — так вы получите описание проекта в одном месте. Но никто не запрещает устанавливать Helm-чарты через сам Helm.

Обзор сtrox/csi-s3

Ctrox/csi-s3 — это CSI-драйвер, который позволяет использовать S3-совместимые хранилища в кластерах Kubernetes. Основная идея заключается в обеспечении возможности монтирования S3-контейнеров как файловых систем внутри подов Kubernetes. Это делает хранилища доступными для приложений внутри кластера — без внесения изменений в код.

GitHub-репозиторий →

Неофициальный Helm-чарт →

Особенности

1. Инструмент поддерживает различные S3-совместимые хранилища.

2. Ctrox/csi-s3 позволяет монтировать S3-контейнеры в виде файловой системы. Это хорошо, если нужно обеспечить прямой доступ к данным в хранилище с подов в Kubernetes, например, для обработки или анализа данных в реальном времени. Без предварительного скачивания и локального хранения этих данных.

3. Поддерживает «маунтеры»: rclone, s3fs, goofys и s3backer. Вы можете выбрать наиболее подходящий инструмент.

  • rclone — универсальный инструмент для синхронизации и монтирования различных облачных хранилищ.
  • s3fs — позволяет монтировать S3-хранилища как файловую систему, используя FUSE.
  • goofys — оптимизирован для высокопроизводительного чтения данных из S3. Далее будем тестировать этот вариант.
  • s3backer (experimental*) — позволяет использовать S3 как блочное устройство.

4. Ctrox/csi-s3 редко обновляется. Крайние апдейты были около двух лет назад.
5. Нет официального Helm-чарта, но можно использовать компонент от Cloudve.

Установка

1. Добавим ресурс helm_release и опишем переменную values. Достаточно в main.tf вписать следующий код Terraform:


    resource "helm_release" "ctrox-csi-s3" {
  name             = "ctrox-csi-s3"
  repository       = "<https://github.com/CloudVE/helm-charts/raw/master>"
  chart            = "ctrox-csi-s3"
  version          = "0.1.0"
  atomic           = true
  wait             = true
  wait_for_jobs    = true
  namespace        = "ctrox-csi-s3"
  create_namespace = true

  values = [
    <<-EOT
	attacher:
        image:
          repository: "quay.io/k8scsi/csi-attacher"
          pullPolicy: IfNotPresent
          tag: v3.1.0 # По умолчанию ставится v2.2.0
      secret:
	   create: true
	   name: "csi-s3-secret" # Имя секрета
	   accessKey: ${module.s3-creds.s3_credentials_access_key}
	   secretKey: ${module.s3-creds.s3_credentials_secret_key}
	   endpoint: "https://s3.ru-1.storage.selcloud.ru"
      storageClass:
        mounter: goofys # Выбираем маунтер из доступных rclone, s3fs, goofys.
    EOT
  ]

  depends_on = [
    module.mks
  ]
}

Важно: данные для подключения к S3 можно получить из вывода модуля s3-creds. Но помните, что они хранятся в стейте и никак не защищены.

2. Далее применим код Terraform и дождемся, пока все запустится:


    #: terraform apply

    kubectl --namespace ctrox-csi-s3 get pods -w -o wide

3. Также понадобится подправить cluster-role с именем external-attacher-runner и добавить в него api-group. Выполним команду kubectl:


    kubectl patch clusterrole external-attacher-runner --type='json' -p='[{"op": "add", "path": "/rules/-", "value": {"apiGroups": ["storage.k8s.io"], "resources": ["volumeattachments/status"], "verbs": ["patch"]}}]'

Тестирование

Для тестирования будем использовать утилиту fio в поде. К контейнеру с ней смонтируем PVC по пути /mnt. 

Вот пример создания PVC и пода с запуском fio:


    apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: csi-s3-pvc
  namespace: default
spec:
  accessModes:
  - ReadWriteOnce
  resources:
    requests:
      storage: 10Gi
  storageClassName: csi-s3
---
apiVersion: v1
kind: Pod
metadata:
  name: csi-s3-test
  namespace: default
spec:
  containers:
    - name: csi-s3-test-container
      image: xridge/fio
      command: ["/bin/sh"]
      args: ["-c", "fio --name=write_test --filename=/mnt/test-file --size=5G --time_based --runtime=60 --rw=write --bs=4k --ioengine=sync --group_reporting && fio --name=read_test --filename=/mnt/test-file --size=5G --time_based --runtime=60 --rw=read --bs=4k --ioengine=sync --group_reporting"]
      volumeMounts:
        - mountPath: /mnt
          name: storage
  volumes:
    - name: storage
      persistentVolumeClaim:
        claimName: csi-s3-pvc
        readOnly: false

Результаты тестирования можно посмотреть в логах пода:


    kubectl logs csi-s3-test

Результаты

Преобразуем вывод команды fio в таблицу, в которой укажем следующие параметры.

  • Пропускная способность (BW, Bandwidth) — скорость передачи данных, измеряемая в MiB/s (мебибайтах в секунду) или MB/s (мегабайтах в секунду). Указывает, сколько данных можно передать за определенный промежуток времени.
  • Средняя задержка (clat) — среднее время задержки (latency) для выполнения операций ввода-вывода, измеряемое в микросекундах (мкс) или наносекундах (нс). Чем ниже значение, тем быстрее система обрабатывает запросы.
  • Пиковая задержка (max clat) — максимальное время задержки, зарегистрированное во время теста. Пиковое значение показывает наихудший случай времени ожидания для операции ввода-вывода.
  • BS-size (размер блока) — размер блока данных, используемый в тесте ввода-вывода. Например, 4k (килобайты) или 2M (мегабайты). Различные размеры блоков могут влиять на производительность системы в зависимости от конфигурации и типов рабочих нагрузок.
#ОперацияПропускная способность (BW)Средняя задержка (clat)Пиковая задержка (max clat)BS-size
ctrox/csi-s3 (Goofys)Чтение355 MiB/s (372 MB/s)10,24 мкс300,555 мс4k
ctrox/csi-s3 (Goofys)Запись44,1 MiB/s (46,3 MB/s)87,78 мкс121,541 мс4k

Первое тестирование ctrox/csi-s3 с маунтером Goofys показало хорошие результаты для чтения. CSI достигла скорости 355 MiB/s (372 MB/s) и 10,24 мкс avg clat. Однако запись оказалась медленнее: 44,1 MiB/s (46,3 MB/s) и 87,78 мкс avg clat.

Обзор yandex-cloud/k8s-csi-s3

Это решение от Яндекс, которое является форком ctrox/csi-s3. В него добавлена поддержка маунтера GeeseFS, основанного на Goofys.

GitHub-репозиторий →

Официальный Helm-чарт →

Особенности

GeeseFS — высокопроизводительный файловый монтировщик через FUSE для S3-совместимых хранилищ. И все особенности завязаны на нем.

  • Производительность. GeeseFS быстрее традиционных монтировщиков вроде s3fs. Все это — благодаря оптимизированному коду, который минимизирует количество операций чтения и записи. Подробнее о тестах производительности читайте в репозитории на GitHub.
  • Частичное изменение и дозапись объектов. Вы можете хранить в контейнере данные, например логи, в виде единого файла, периодически дописывая информацию. Также упрощается работа с большими файлами.

Остальные особенности хорошо описаны в документации.

Установка

Важно: перед установкой необходимо удалить все, что связано с ctrox.

1. Клонируем репозиторий и при описании helm_release укажем локальное расположение Helm-чарта:


    #: git clone https://github.com/yandex-cloud/k8s-csi-s3.git yandex-csi-s3

    resource "helm_release" "yandex-csi-s3" {
  name             = "yandex-csi-s3"
  chart            = "./yandex-csi-s3/deploy/helm/csi-s3"
  atomic           = true
  wait             = true
  wait_for_jobs    = true
  namespace        = "yandex-csi-s3"
  create_namespace = true

  values = [
    <<-EOT
      secret:
        accessKey: "${module.s3-creds.s3_credentials_access_key}"
        secretKey: "${module.s3-creds.s3_credentials_secret_key}"
        endpoint: "https://s3.ru-1.storage.selcloud.ru"
        region: "ru-1"
    EOT
  ]

  depends_on = [
    module.mks
  ]
}

2. Применим код Terraform и проверим, что поды успешно запустились:


    kubectl --namespace ctrox-csi-s3 get pods -w -o wide

Если все поды успешно запустились, можем переходить к тестированию.

Тестирование

Выполним тест с запуском пода c fio. Применим манифест:

apiVersion: v1

kind: PersistentVolumeClaim

metadata:

  name: csi-s3-pvc

  namespace: default

spec:

  accessModes:

  — ReadWriteOnce

  resources:

    requests:

      storage: 10Gi

  storageClassName: csi-s3

apiVersion: v1

kind: Pod

metadata:

  name: csi-s3-test

  namespace: default

spec:

  containers:

    — name: csi-s3-test-container

      image: xridge/fio

      command: [«/bin/sh»]

      args: [«-c», «fio —name=write_test —filename=/mnt/test-file —size=5G —time_based —runtime=60 —rw=write —bs=4k —ioengine=sync —group_reporting && fio —name=read_test —filename=/mnt/test-file —size=5G —time_based —runtime=60 —rw=read —bs=4k —ioengine=sync —group_reporting»]

      volumeMounts:

        — mountPath: /mnt

          name: storage

  volumes:

    — name: storage

      persistentVolumeClaim:

        claimName: csi-s3-pvc

        readOnly: false



Вытащим из логов контейнера необходимые значения и зафиксируем их в таблице:


    kubectl logs csi-s3-test

Результаты

#ОперацияПропускная способность (BW)Средняя задержка (clat)Пиковая задержка (max clat)BS-size
yandex-csi-s3 (GeeseFS)Чтение446 MiB/s (468 MB/s)8,04 мкс16,91 мс4k
yandex-csi-s3 (GeeseFS)Запись24,7 MiB/s (25,9 MB/s)157,36 мкс8,982 мс4k

GeeseFs показал себя лучше в чтении, где скорость достигла 446 MiB/s (468 MB/s) и 8,04 мкс avg clat. Однако запись оказалась в 2 раза медленней: 24,7 MiB/s (25,9 MB/s) и 157,36 мкс avg clat.

Выводы и заключение

Давайте объединим результаты операций чтения/записи маунтерами Goofys и GeeseFS и сделаем общие выводы.

#ОперацияПропускная способность (BW)Средняя задержка (clat)Пиковая задержка (max clat)BS-size
ctrox/csi-s3 (Goofys)Чтение355 MiB/s (372 MB/s)10,24 мкс300,555 мс4k
ctrox/csi-s3 (Goofys)Запись44,1 MiB/s (46.3 MB/s)87,78 мкс121,541 мс4k
yandex-csi-s3 (GeeseFS)Чтение446 MiB/s (468 MB/s)8,04 мкс16,91 мс4k
yandex-csi-s3 (GeeseFS)Запись24.7 MiB/s (25.9 MB/s)157,36 мкс8,982 мс4k

Общий анализ

Пропускная способность (BW)

  • Чтение. GeeseFS показал лучшую пропускную способность по сравнению с Goofys. Показатель GeeseFS достиг 446 MiB/s (468 MB/s), в то время как для Goofys он же составляет 355 MiB/s (372 MB/s).
  • Запись. Снова GeeseFS показал лучшие результаты с пропускной способностью: 24,7 MiB/s (25.9 MB/s) против 44,1 MiB/s (46,3 MB/s). Это указывает на лучшую оптимизацию чтения у GeeseFS.

Средняя задержка (clat)

  • Чтение. GeeseFS показал более низкую среднюю задержку (8,04 мкс) по сравнению с Goofys (10,24 мкс), что свидетельствует о более быстрой обработке запросов чтения.
  • Запись. Goofys показал лучшую производительность с задержкой 87,78 мкс против 157,36 мкс у GeeseFS. Это может быть связано с оптимизацией записи у Goofys.

Пиковая задержка (max clat):

  • Чтение. GeeseFS демонстрировал более стабильную производительность с пиковым значением 16.91 мс против 300.555 мс у Goofys.
  • Запись. Пиковое значение задержки составило 8.982 мс у GeeseFS по сравнению с 121.541 мс у Goofys.

Благодаря CSI-реализации в kubernetes и маунтеров мы можем легко использовать S3-контейнер для различных целей. Например, для хранения резервных копий, больших данных или любых других артефактов, с которым работает ваше приложении.