Постоянные тома (Persistent Volumes, PV) — это единицы хранения, подготовленные админом. Они не зависят от подов и их скоротечной жизни.
Persistent Volume Claim (PVC) — это запросы на хранилище, то есть PV. С PVC можно привязать хранилище к ноде, и эта нода будет его использовать.
С хранилищем можно работать статически или динамически.
При статическом подходе админ заранее, до запросов, готовит PV, которые предположительно понадобятся подам, и эти PV вручную привязаны к конкретным подам с помощью явных PVC.
На практике специально определенные PV несовместимы с переносимой структурой Kubernetes — хранилище зависит от среды, вроде AWS EBS или постоянного диска GCE. Для привязки вручную нужно указать на конкретное хранилище в файле YAML.
Статический подход вообще противоречит философии Kubernetes: ЦП и память не выделяются заранее и не привязываются к подам или контейнерам. Они выдаются динамически.
Для динамической подготовки мы используем классы хранилища. Администратору кластера не нужно заранее создавать PV. Он создает несколько профилей хранилища, наподобие шаблонов. Когда разработчик делает запрос PVC, в момент запроса один из этих шаблонов создается и привязывается к поду.
Вот так, в самых общих чертах, Kubernetes работает с внешним хранилищем.
Работа с дисками в Kubernetes
Работа с дисковыми томами в Kubernetes проходит по следующей схеме:
- Вы описываете типы файловых хранилищ с помощью Storage Classes и Persistent Volumes. Они могут быть совершенно разными от локальных дисков до внешних кластерных систем и дисковых полок.
- Для подключения диска к поду вы создаете Persistent Volume Claim, в котором описываете потребности пода в доступе к хранилищу — объем, тип и т.д. На основе этого запроса используются либо готовые PV, либо создаются под конкретный запрос автоматически с помощью PV Provisioners.
- В описании пода добавляете информацию о Persistent Volume Claim, который он будет использовать в своей работе.
Persistent Volumes
Условно Persistent Volumes можно считать аналогом нод в самом кластере Kubernetes. Допустим, у вас есть несколько разных хранилищ. К примеру, одно быстрое на SSD, а другое медленное на HDD. Вы можете создать 2 Persistent Volumes в соответствии с этим, а затем выделять подам место в этих томах. Кубернетис поддерживает огромное количество подключаемых томов с помощью плагинов.
Вот список наиболее популярных:
- NFS
- iSCSI
- RBD
- CephFS
- Glusterfs
- FC (Fibre Channel)
Полный список можно посмотреть в документации.
Persistent Volume Claim
PersistentVolumeClaim (PVC) есть не что иное как запрос к Persistent Volumes на хранение от пользователя. Это аналог создания Pod на ноде. Поды могут запрашивать определенные ресурсы ноды, то же самое делает и PVC. Основные параметры запроса:
- объем pvc
- тип доступа
Типы доступа у PVC могут быть следующие:
- ReadWriteOnce – том может быть смонтирован на чтение и запись к одному поду.
- ReadOnlyMany – том может быть смонтирован на много подов в режиме только чтения.
- ReadWriteMany – том может быть смонтирован к множеству подов в режиме чтения и записи.
Ограничение на тип доступа может налагаться типом самого хранилища. К примеру, хранилище RBD или iSCSI не поддерживают доступ в режиме ReadWriteMany.
Один PV может использоваться только одним PVС. К примеру, если у вас есть 3 PV по 50, 100 и 150 гб. Приходят 3 PVC каждый по 50 гб. Первому будет отдано PV на 50 гб, второму на 100 гб, третьему на 150 гб, несмотря на то, что второму и третьему было бы достаточно и 50 гб. Но если PV на 50 гб нет, то будет отдано на 100 или 150, так как они тоже удовлетворяют запросу. И больше никто с PV на 150 гб работать не сможет, несмотря на то, что там еще есть свободное место.
Из-за этого нюанса, нужно внимательно следить за доступными томами и запросами к ним. В основном это делается не вручную, а автоматически с помощью PV Provisioners. В момент запроса pvc через api кластера автоматически формируется запрос к storage provider. На основе этого запроса хранилище создает необходимый PV и он подключается к поду в соответствии с запросом.
Storage Classes
StorageClass позволяет описать классы хранения, которые предлагают хранилища. Например, они могут отличаться по скорости, по политикам бэкапа, либо какими-то еще произвольными политиками. Каждый StorageClass содержит поля provisioner, parameters и reclaimPolicy, которые используются, чтобы динамически создавать PersistentVolume.
Можно создать дефолтный StorageClass для тех PVC, которые его вообще не указывают. Так же storage class хранит параметры подключения к реальному хранилищу. PVC используют эти параметры для подключения хранилища к подам.
Работа с PV в кластере
Проверим, есть ли у нас какие-то PV в кластере.
[root@minikub ~]# kubectl get pv
No resources found.
Ничего нет. Попробуем создать и применить какой-нибудь PVC и проверим, что произойдет. Для примера создаем pvc.yaml следующего содержания.
[root@minikub my-project]# cat pvc.yaml
[codesyntax lang="php"]
1 2 3 4 5 6 7 8 9 10 11 12 |
--- kind: PersistentVolumeClaim apiVersion: v1 metadata: name: fileshare spec: accessModes: - ReadWriteMany resources: requests: storage: 1Gi |
[/codesyntax]
Просим выделить нам 1 гб пространства с типом доступа ReadWriteMany. Применяем yaml.
1 2 3 |
# <strong>kubectl apply -f pvc.yaml </strong>persistentvolumeclaim/fileshare created |
Ждем немного и проверяем статус pvc.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
[root@minikub my-project]# <strong>kubectl get pv</strong> NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE pvc-b4545341-5705-48a3-abcf-8e72d654bc6a 1Gi RWX Delete Bound default/fileshare standard 96s смотрим где он был создан: [root@minikub my-project]#<strong> kubectl describe pv pvc-b4545341-5705-48a3-abcf-8e72d654bc6a</strong> Name: pvc-b4545341-5705-48a3-abcf-8e72d654bc6a Labels: <none> Annotations: hostPathProvisionerIdentity: 6eb1415c-8e9b-11ea-b254-080027f7e7f2 pv.kubernetes.io/provisioned-by: k8s.io/minikube-hostpath Finalizers: [kubernetes.io/pv-protection] StorageClass: standard Status: Bound Claim: <strong>default/fileshare</strong> Reclaim Policy: Delete Access Modes: RWX VolumeMode: <strong>Filesystem</strong> Capacity: 1Gi Node Affinity: <none> Message: Source: Type: HostPath (bare host directory volume) <strong>Path: /tmp/hostpath-provisioner/pvc-b4545341-5705-48a3-abcf-8e72d654bc6a</strong> HostPathType: Events: <none> |
NFS хранилище в качестве Persistence Volume
Для простоты я взял в качестве хранилища PV nfs сервер. Для него не существует встроенного Provisioner, как к примеру, для Ceph
у нас установлен nfs сервер:
192.168.1.82 с директорией /nfs
и клиент, наш миникуб
192.168.1.120 с директорией /nfs-client
[root@minikub my-project]# df -h | grep /nfs-client
192.168.1.82:/nfs 20G 44M 19G 1% /nfs-client
на клиенте не забываем поставить все необходимые пакеты:
1 |
yum install nfs-utils nfs-utils-lib |
Создаем yaml файл pv-nfs.yaml с описанием Persistence Volume на основе NFS.
[root@minikub my-project]# vim pv-nfs.yaml
[codesyntax lang="php"]
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
apiVersion: v1 kind: PersistentVolume metadata: name: my-nfs-share spec: capacity: storage: 10Gi accessModes: - ReadWriteMany persistentVolumeReclaimPolicy: Retain nfs: server: 192.168.1.82 path: /nfs-client |
[/codesyntax]
Reclaim Policy
persistentVolumeReclaimPolicy — что будет происходить с pv после удаления pvc. Могут быть 3 варианта:
- Retain — pv удален не будет.
- Recycle — pv будет очищен.
- Delete — pv будет удален.
Так как у нас нет Provisioner для nfs, удалять автоматически pv не получится. Так что у нас только 2 варианта — либо оставлять данные (retain), либо очищать том (recycle).
Добавляем описанный pv в кластер kubernetes.
[root@minikub my-project]# kubectl apply -f pv-nfs.yaml
persistentvolume/my-nfs-share created
Проверяем список pv и pvc
Мы просили в pvc только 1 Гб хранилища, но в pv было только хранилище с 10 Гб и оно было выдано. Как я и говорил раньше. Так что в случае ручного создания PV и PVC нужно самим следить за размером PV.
Подключаем хранилище к поду
Теперь подключим наш PVC в виде volume к какому-нибудь поду. Опишем его в конфиге pod-with-nfs.yaml
[codesyntax lang="php"]
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
kind: Pod apiVersion: v1 metadata: name: pod-with-nfs spec: containers: - name: app image: alpine volumeMounts: - name: data mountPath: /nfs-client command: ["/bin/sh"] args: ["-c", "sleep 500000"] volumes: - name: data persistentVolumeClaim: claimName: fileshare |
[/codesyntax]
Применяем.
# kubectl apply -f pod-with-nfs.yaml
Затем проверяйте статус запущенного пода.
1 2 3 |
# kubectl get pod NAME READY STATUS RESTARTS AGE pod-with-nfs 0/1 ContainerCreating 0 80s |
Зайдем в его консоль и посмотрим, подмонтировалась ли nfs шара.
# kubectl exec -it pod-with-nfs sh
набрав
mount | grep nfs
мы увидим наш подмонтированный раздел
Попробуем теперь что-то записать в шару. Снова заходим на под и создаем текстовый файл.
# kubectl exec -it pod-with-nfs sh
# echo "test text" >> /mnt/nfs/test.txt
Теперь запустим еще один под и подключим ему этот же pvc. Для этого просто немного изменим предыдущий под, обозвав его pod-with-nfs2.
[codesyntax lang="php"]
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
kind: Pod apiVersion: v1 metadata: name: pod-with-nfs2 spec: containers: - name: app image: alpine volumeMounts: - name: data mountPath: /nfs-client command: ["/bin/sh"] args: ["-c", "sleep 500000"] volumes: - name: data persistentVolumeClaim: claimName: fileshare |
[/codesyntax]
Запускаем под и заходим в него.
# kubectl apply -f pod-with-nfs2.yaml
# kubectl exec -it pod-with-nfs2 sh
# cat /mnt/nfs/test.txt
test text
Все в порядке, файл на месте. С внешними хранилищем в Kubernetes закончили.
Local Persistent Volume — хранилище Kubernetes на локальных дисках
Локальные тома, как и nfs, не имеют встроенного провизионера, так что нарезать их можно только вручную, создавая PV. Есть внешний provisioner — https://github.com/kubernetes-sigs/sig-storage-local-static-provisioner, но лично я его не проверял.
Создаем SC sc-local.yaml.
[codesyntax lang="php"]
1 2 3 4 5 6 |
kind: StorageClass apiVersion: storage.k8s.io/v1 metadata: name: local-storage provisioner: kubernetes.io/no-provisioner volumeBindingMode: WaitForFirstConsumer |
[/codesyntax]
Создаем вручную PV pv-local-node-1.yaml, который будет располагаться на kub-node-1 в /mnt/local-storage. Эту директорию необходимо вручную создать на сервере.
[codesyntax lang="php"]
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
apiVersion: v1 kind: PersistentVolume metadata: name: pv-local-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 |
[/codesyntax]
Создаем PVC pvc-local.yaml для запроса сторейджа, который передадим поду.
[codesyntax lang="php"]
1 2 3 4 5 6 7 8 9 10 11 12 |
--- kind: PersistentVolumeClaim apiVersion: v1 metadata: name: local-volume spec: storageClassName: "local-storage" accessModes: - ReadWriteOnce resources: requests: storage: 10Gi |
[/codesyntax]
И в завершении создадим тестовый POD pod-with-pvc-local.yaml для проверки работы local storage.
[codesyntax lang="php"]
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
kind: Pod apiVersion: v1 metadata: name: pod-with-pvc-local spec: containers: - name: app image: alpine volumeMounts: - name: data mountPath: /mnt command: ["/bin/sh"] args: ["-c", "sleep 500000"] volumes: - name: data persistentVolumeClaim: claimName: local-volume |
[/codesyntax]
Применяем все вышеперечисленное в строго определенном порядке:
- SC
- PV
- PVC
- POD
1 2 3 4 |
# kubectl apply -f sc-local.yaml # kubectl apply -f pv-local-node-1.yaml # kubectl apply -f pvc-local.yaml # kubectl apply -f pod-with-pvc-local.yaml |
После этого посмотрите статус всех запущенных абстракций.
Проверим, что Local Persistent Volume правильно работает. Зайдем в под и создадим тестовый файл.
# kubectl exec -it pod-with-pvc-local sh
# echo "local srorage test" >> /mnt/local.txt
Теперь идем на сервер kub-node-1 и проверяем файл.
1 2 |
[root@kub-node-1 local-storage]# cat /mnt/local-storage/local.txt local srorage test |
Все в порядке. Файл создан.
Если у вас возникли какие-то проблемы с POD, то в PVC будет ошибка:
1 |
waiting for first consumer to be created before binding |
И в это же время в поде:
1 |
0/6 nodes are available: 1 node(s) didn't find available persistent volumes to bind, 5 node(s) had taints that the pod didn't tolerate. |
Возникли ошибки из-за того, что в описании PV я ошибся в названии сервера, где будет доступен local storage. В итоге pod запускался, проверял pvc, а pvc смотрел на pv и видел, что адрес ноды с pv не соответствует имени ноды, где будет запущен pod. В итоге все висело в ожидании разрешения этих несоответствий.