Kubernetes пожалуй является самым распространенным средством оркестрации контейнеризированных приложений. С его помощью можно автоматизировать развертывание, масштабирование и координацию работы с контейнером в условиях кластера. Благодаря этим свойствам Kubernetes (который также называют k8s) существенно сокращает время сборки, поставки и масштабирования контейнерных приложений.
Одним из замечательных свойств контейнеров k8s является их неизменяемость, то есть, если мы закрываем контейнер, то вся информация, хранящаяся в нем теряется. Это очень удобно для всевозможных тестовых и учебных сред, решений типа «песочница» и аналогичных. Суть сводится к тому, что что бы мы не делали с контейнером, после его закрытия все будет уничтожено. Мы можем запустить подозрительный файл в контейнере не опасаясь за последствия (если конечно у нас правильно настроено монтирование) и через пару минут уничтожить этот контейнер, также без последствий.
Однако существует также еще масса приложений, таких как базы данных, для которых требуется сохранение результатов работы контейнера, проще говоря, требуется постоянное хранилище. В этой статье мы подробно рассмотрим работу с постоянными хранилищами в Kubernetes.
Что такое хранилище в K8s
Для работы с постоянными томами в Kubernetes имеются объекты API позволяющие на постоянной основе выделять ресурсы хранения и управлять ими. Начнем с подсистемы Persistent Volumes (PV).
Данная подсистема позволяет создавать ресурс хранения, который не зависит от того, какой модуль использует его. Использование PV гарантирует, что хранилище останется постоянным. При этом PV могут быть динамически предоставлены с помощью классов хранилища (о них мы поговорим чуть дальше), а могут быть подготовлены администраторами вручную. Например, если у вас имеется несколько физических хранилищ, допустим быстрые SSD диски и медленные HDD. Тогда мы можем создать два PV и в зависимости от выполняемых приложениями задач выделять подам с соответствующими контейнерами место в этих томах. При этом стоит заметить, что Kubernetes умеет работать с множеством различных томов, такими как NFS, iSCSI, Fibre Channel и другими.
Еще одно понятие k8s, которое нам необходимо для работы с хранилищами это StorageClass (SC). С помощью SC можно описать классы хранения, которые предлагают хранилища. Как уже упоминалось, хранилища могут отличаться по скорости, по политикам бэкапа, либо какими‑то еще произвольными политиками. Каждый StorageClass содержит поля provisioner, parameters и reclaimPolicy, которые используются, чтобы динамически создавать PersistentVolume.
В качестве примера создадим Local Persistent Volume — хранилище Kubernetes на локальных дисках. Для промышленной эксплуатации это, наверное, не самая лучшая идея, а вот для разработки в контейнерах и отладки вполне подойдет.
Сначала объявим StorageClass в файле sc.yaml:
kind: StorageClass
apiVersion: storage.k8s.io/v1
metadata:
name: mystorage
provisioner: kubernetes.io/no-provisioner
volumeBindingMode: WaitForFirstConsumer
Обратите внимание на значение параметра provisioner — no provisioner. В k8s отсутствует встроенный провизионер для создания локальных хранилищ.
Применим созданный файл настроек.
# kubectl apply -f sc.yaml
Далее нам необходимо создать Persistent Volume которое будет использовать ресурсы нашего локального хранилища. Для этого создадим файл pvlocal-1.yaml с описанием PV, который будет располагаться на сервере node-1 в /mnt/local‑storage. Естественно, этот каталог должен быть создан на данном сервере.
apiVersion: v1
kind: PersistentVolume
metadata:
name: node-1
spec:
capacity:
storage: 10Gi
volumeMode: Filesystem
accessModes:
- ReadWriteOnce
persistentVolumeReclaimPolicy: Retain
storageClassName: local-storage
local:
path: /mnt/local-storage
nodeAffinity:
required:
nodeSelectorTerms:
- matchExpressions:
- key: kubernetes.io/hostname
operator: In
values:
- kub-node-1
Мы создали Persistent Volume в нашем Kubernetes, далее посмотрим, как можно использовать данное хранилище.
Также применим созданный файл.
# kubectl apply -f pvlocal.yaml
PersistentVolumeClaim
Теперь, когда мы создали PV мы можем попробовать воспользоваться его ресурсами. Пользователь может сам запросить у PV ресурсы для хранения данных. Такая подсистема называется PersistentVolumeClaim (PVC). При этом важно понимать, что PVC может потреблять только тот ресурс PV, который соответствует требованиям к его размеру и режимам доступа. Если соответствующий PV доступен, он привязывается к PVC. В противном случае Kubernetes динамически подготавливает PV (если это технически возможно) или запрос завершается ошибкой. Обычно запрашивают следующие параметры: требуемый объем хранилища pvc и тип доступа.
Типы доступа у PVC могут быть следующие:
ReadWriteOnce — том может быть смонтирован на чтение и запись к одному поду.
ReadOnlyMany — том может быть смонтирован на много подов в режиме только чтения.
ReadWriteMany — том может быть смонтирован к множеству подов в режиме чтения и записи.
По поводу технических возможностей стоит отметить, что ограничения по типу доступа могут зависеть и от типа хранилища. Так, к примеру на iSCSI нельзя использовать ReadWriteMany.
Довольно теории, вернемся к нашему PV и рассмотрим пример запроса PVC c 2Гб и типом доступа ReadWriteOnce.
---
kind: PersistentVolumeClaim
apiVersion: v1
metadata:
name: mypvc
spec:
accessModes:
- ReadWriteOnce
volumeMode: Filesystem
resources:
requests:
storage: 2Gi
Сохраняемся в файле pvc.yaml и применяем данный файл с помощью команды:
kubectl apply -f pvc.yaml
И посмотрим, результат:
# kubectl get pvc
Как видно, запрашиваемое PVC на 2 Гигабайта нам успешно выделили. Теперь необходимо проверить работу нашего хранилища, для этого мы создадим сначала один под использующий pvc, в нем создадим текстовый файл. Затем немного поправив описание создадим второй под, использующий этот же PVC и убедимся в доступности в нем созданного файла.
Наши поды будут запускать /bin/bash и делать довольно продолжительную паузу. Для этого сначала создаем файл pod‑pvc.yaml следующего содержания:
kind: Pod
apiVersion: v1
metadata:
name: pod-pvc
spec:
containers:
- name: app
image: alpine
volumeMounts:
- name: mystorage
mountPath: /mnt
command: ["/bin/sh"]
args: ["-c", "sleep 100000"]
volumes:
- name: mystorage
persistentVolumeClaim:
claimName: mypvc
Создадим наш под с помощью следующей команды:
# kubectl apply -f pod-pvc.yaml
Для проверки работы нашего Local Persistent Volume мы создадим текстовый файл.
# kubectl exec -it pod-pvc sh
# echo "test file created" >> /mnt/local.txt
Теперь немного поправим наш файл для создания пода: поменяем значение name на pod-pvc2 и применим его с помощью kubectl.
kind: Pod
apiVersion: v1
metadata:
name: pod-pvc2
spec:
containers:
- name: app
image: alpine
volumeMounts:
- name: mystorage
mountPath: /mnt
command: ["/bin/sh"]
args: ["-c", "sleep 100000"]
volumes:
- name: mystorage
persistentVolumeClaim:
claimName: mypvc
и
# kubectl apply -f pod-pvc.yaml
Посмотрим, как себя чувствуют наши поды:
# kubectl get pod
Зайдем на pod-pvc2 и посмотрим содержимое /mnt/local.txt
# kubectl exec -it pod-pvc2 sh
Как видно файл и его содержимое на месте.
Заключение
В этой статье мы рассмотрели основные моменты, связанные с созданием постоянных хранилищ в Kubernetes. Теперь вы можете без проблем использовать Persistent Volumes для своих приложений в K8s.
Также хочу пригласить всех на бесплатный урок, в рамках которого изучим тонкости архитектурного устройства kubernetes, поговорим о концепциях data и control plane и подробнее остановимся на ключевом компоненте — etcd и алгоритме консенсуса raft.