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. Это делает хранилища доступными для приложений внутри кластера — без внесения изменений в код.
Особенности
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.
Особенности
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-контейнер для различных целей. Например, для хранения резервных копий, больших данных или любых других артефактов, с которым работает ваше приложении.