Буквально в прошлой заметке поднимали новый кластер куба используя инструмент kubespray. Сегодня же проработаем задачу постоянных томов для этого кластера. Напомню, что это было три виртуалки, размещенные на vmware инфраструктуре. И сразу же появилась идея провижинить постоянные тома в vSphere, у которой начиная с версии vSphere 6.7 (Update3) стала доступна поддержка Cloud Native Storage (CNS).

Немного теории

Cloud Native Storage (CNS) - это некая прослойка между vSphere и K8s, которая дает нам возможности для создания и управления постоянными томами в экосистеме vSphere. k8s-csi-schemes.png (Компонентная схема, для понимания)

CNS включает в себя два основных компонента:

  • CNS Control plane - отвечает за управление жизненным циклом постоянных томов, включая такие операции как - создание, удаление, чтение и обновления. Также позволяет управлять метаданными томов, создавать снепшоты, выполнять операции копирования или клонирония томов. Ну и отвечает за мониторинг работоспособности этих же томов. Для выделения томов используются Storage Policy.

    Администратор vSphere создает политики в Storage Policy, в которых определяются правила использования хранилища для pv в kubernetes. И пользователь кластера самостоятельно выделяет себе хранилище под свое приложение в соотвествии с установленной политикой.

  • CSI (Container storage interface) - плагин, который устанавливается в кластере k8s и отвечает за выделение постоянного тома, монтирование/отключение этого тома к/от виртуальной машины. Ну и монтирование/размонтированного тома во внутрь пода из под виртуальной тачки.

На самом деле есть за кадром остался еще один компонент - vSphere Cloud Provider (CPI). Этот провайдер используется для подключения к vSphere и сопостовления некой карты нашей инфраструктуры, наличие виртуалок, дисков и прочего.

При поисках информации об CPI, приходишь к какой то путанице, потому что есть In-tree vSphere Cloud Provider и Out-tree vSphere Cloud Provider.

В первом случаи (in-tree) провайдер уже включен в основной репозиторий k8s и нам не нужно инсталить дополнительные компоненты. Что бы начать пользоваться пропижинингом достаточно указать нужный Storage Class в конфигурации. Имея нужный конфиг, компоненты - kube-apiserver, kubelet, и другие уже нативно понимают как им нужно подключится к vCenter серверу. k8s-vsphere-in-tree-architecture.png Во втором же случаи (out-tree) концепция не предполагает наличие драйвера или плагина включенного в k8s. Решением является установка отдельных компонентов CPI(Cloud Provider Interface) и CSI(Container Storage Interface). Тут только cloud-controller-manager имеет доступ к конфигурационному файлу и учеткам для подключения к vCenter серверу. k8s-vsphere-out-of-tree-architecture.png

По причине появления большого количества различных Cloud провайдеров, добавлять функции для каждого в поддержку k8s стало не практичным и не рекомендуется. Новые реализации провайдеров должны следовать концепции out-tree.

Подготовка vSphere

Достаточно теории, произведем настройки на стороне vSphere клиента.

Storage policy и теги

Мы не будем использовать vSAN, хотя относительно этого очень много инфы представлено, здесь же тома будем размещать на традиционный VMFS датастор.

Под постоянные тома у меня зарезервирован определенный датастор. Для размещение данных на этом датасторе, нужно создать новую Storage Polycy. Эта политика будет связываться с нужным мне датастором по определенному тегу. В storage классе куба будем использовать имя этой политики для биндинга. Ниже я добавил схему взаимосвязи объектов: k8s-vsphere-st-policy-schemes.png

Теперь идем в vSphere клиент, раздел с тегами и атрибутами. Провалившись во внутрь, жмем на кнопку создания нового тега: k8s-vsphere-st-policy-tag2.png

Откроется новое окно, и здесь же в поле name указываем значение для нашего тега. Далее жмем на создание новой категории. k8s-vsphere-st-policy-tag3.png Откроется новое окно для добавления категории. Указываем имя категории и в списке ниже объекты этой категории. k8s-vsphere-st-policy-cat1.png

После создания новой категории возвращаемся в окно добавления тега, в разделе Category теперь нужно выбрать только что созданную категорию: k8s-vsphere-st-policy-tag4.png

Тег, который только что создали теперь нужно навесить на датастор. Переходим в раздел со всеми датасторами, выбираем нужны и вешаем тег. k8s-vsphere-st-policy-tag6.png

И наконец, создаем новую Storage политику: k8s-vsphere-st-policy1.png

В окне создания политики указываем имя будущей полиси и жмем далее: k8s-vsphere-st-policy2.png

На втором поинте этого же окна включаем правило на основе тегов и жмем далее: k8s-vsphere-st-policy3.png

В третьем поинте уже выбираем категорию. В опциях указываем, что хотим использовать хранилище с тегом. И выбираем созданный нами тег. k8s-vsphere-st-policy4.png

Теперь, если все сделали правильно, отобразится список доступных для нас датасторов. У нас он один, поэтому жмем далее и подтверждаем создание. k8s-vsphere-st-policy5.png

Новый пользователь

Для доступа со стороны кластера к функциям Cloud Native Storage, требуется создать пользователя с определенным набором привилегей.

Переходим в менеджмент пользоватей и создаем нового локального пользователя. k8s-vsphere-user1.png

В новом окне указываем логин/пароль для юзера: k8s-vsphere-user2.png

Последующим этапом требуется создать роли. Весь список требуемых ролей можно посмотреть в документации. Но я ограничусь созданием только необдимых для работы. И что бы не заграмождать этот пост, не буду демонстрировать их поэтапное создание, а только перечислю роли требуемые в контексти этой задачи:

  • CNS-Datastore - Эта роль позволит выполнять операции чтения, записи, удаления и изменение имени объектов на датасторе. k8s-vsphere-st-roles1.png
  • CNS-VM - роль позволяющая изменить конфигурацию виртуальной машины, именно добавить/удалить диск. k8s-vsphere-st-roles2.png
  • CNS-SEARCH-AND-SPBM - а эта роль, позволяет администратору хранилища смотреть в UI - Cloud Native Storage. Также в этой роли разрешаем просмотр определенных политик хранения. k8s-vsphere-st-roles3.png

Ну и наконец, объединяем все в воедино. На объекты vSphere (хосты, датастор и сам vCenter) добавляем права для этого пользователя с указанием ранее созданных ролей. Как я ранее говорил под k8s кластера у меня подготовлен отдельный датастор, находим его из всего списка и в permissions добавляем созданного пользователя с ролью CNS-VM. k8s-vsphere-st-perm1.png

По аналогии с датастором, у меня также ранее был подготовлен кластер из нескольких серверов под k8s. В настройках этого кластера выдаем права для нашего пользователя с ролью CNS-VM. k8s-vsphere-st-perm2.png

Заверщающим этапом, выдаем права пользователю на просмотр политик. На vCenter сервере назначем права пользователю с ролью CNS-SEARCH-AND-SPBM: k8s-vsphere-st-perm3.png

Стоит отметить, что если не хотите особо заморачиваться с ролями и пользователями, то можно воспользоваться уже существущим пользователем с админскими правами на vCenter.

Но в документации рекомендуется ограничивать полномочия, по бест практикс.

Включение расширенного UUID

На каждой виртуальной тачке, которая входит в состав k8s кластера в настройках должен быть включен параметр - disk.EnableUUID. Нужно это для того, что бы у постоянного тома всегда был согласованный UUID.

Выключаем виртуалку, и переходим в ее настройки. В настройках нужно провалиться в раздел advanced и внутри раздела выбрать - edit configuration: k8s-vsphere-vm-conf1.png

В новом окне с конфигурацией, добавляем переменную disk.EnableUUID со значением True и сохраняемся. k8s-vsphere-vm-conf2.png

Сохраняемся и вновь запускаем виртуалку. И еще один момент который стоит проверить, это Compatibillity VM версия. Она должна быть выше 15 версии.

Этот момент тоже стоит прочекать заранее. k8s-vsphere-vm-conf3.png

Настройка на стороне K8s

Все подготовительные таски на стороне vSphere завершены. Далее приступаем к установке дополнительных компонентов на стороне кластера.

Установка CPI

Перед установкой интерфейса Cloud провайдера, всем нодам нужно проставить метку (taint) - node.cloudprovider.kubernetes.io/uninitialized=true:NoSchedule. С помошью этой метки, мы запрещаем размещение новых подов кубелетом. Эта одна из процедур, которая используется для предотвращения различных проблем на узлах кластера.

После установки провайдера эту метку можно снять.

Затейнить ноды можно командой:

## Просмотреть текущие лейблы:
[root@k8s-node1 ~]# kubectl describe nodes | egrep "Taints:|Name:"
Name:               k8s-node1
Taints:             node-role.kubernetes.io/control-plane:NoSchedule
Name:               k8s-node2
Taints:             <none>
Name:               k8s-node3
Taints:             <none>

# Помечаем наши ноды
[root@k8s-node1 ~]# kubectl taint node k8s-node1 node.cloudprovider.kubernetes.io/uninitialized=true:NoSchedule
node/k8s-node1 tainted
[root@k8s-node1 ~]# kubectl taint node k8s-node2 node.cloudprovider.kubernetes.io/uninitialized=true:NoSchedule
node/k8s-node2 tainted
[root@k8s-node1 ~]# kubectl taint node k8s-node3 node.cloudprovider.kubernetes.io/uninitialized=true:NoSchedule
node/k8s-node3 tainted

Затем качаем манифест для CPI:

[root@k8s-node1 ~]# export VERSION=1.26
[root@k8s-node1 ~]# wget https://raw.githubusercontent.com/kubernetes/cloud-provider-vsphere/master/releases/v$VERSION/vsphere-cloud-controller-manager.yaml

В скачанном манифесте нужно отредактировать объекты Secret и ConfigMap. Основные изменения, это указание IP-адреса vCenter сервера и пользователя с паролем.

Редактируем манифест:

[root@k8s-node1 ~]# vim vsphere-cloud-controller-manager.yaml
---
apiVersion: v1
kind: Secret
metadata:
  name: vsphere-cloud-secret
  labels:
    vsphere-cpi-infra: secret
    component: cloud-controller-manager
  namespace: kube-system
  # NOTE: this is just an example configuration, update with real values based on your environment
stringData:
  10.23.30.18.username: "k8s-cns-user@local"
  10.23.30.18.password: "password"
---
apiVersion: v1
kind: ConfigMap
metadata:
  name: vsphere-cloud-config
  labels:
    vsphere-cpi-infra: config
    component: cloud-controller-manager
  namespace: kube-system
data:
  # NOTE: this is just an example configuration, update with real values based on your environment
  vsphere.conf: |
    # Global properties in this section will be used for all specified vCenters unless overriden in VirtualCenter section.
    global:
      port: 443
      # set insecureFlag to true if the vCenter uses a self-signed cert
      insecureFlag: true
      # settings for using k8s secret
      secretName: vsphere-cloud-secret
      secretNamespace: kube-system

    # vcenter section
    vcenter:
      vCenter01:
        server: 10.23.30.18
        user: "k8s-cns-user@local"
        password: "password"
        datacenters:
          - Office
---

В манифесте с описанием полей для Secret, редактируем секцию stringData:

stringData:
  10.23.30.18.username: "k8s-cns-user@local"
  10.23.30.18.password: "password"

А в конфигмапе, в секции vcenter: прописываем поля доступа к серверу:

vcenter:
  vCenter01:
    server: 10.23.30.18
    user: "k8s-cns-user@local"
    password: "password"
    datacenters:
      - Office

Далее применяем манифест к кластеру:

[root@k8s-node1 ~]# kubectl apply -f vsphere-cloud-controller-manager.yaml

Если все сделано правильно, создадутся новые объекты. А в листинге появится новый запущенный под:

[root@k8s-node1 ~]# kubectl get po -A | grep vsphere-cloud
---
kube-system   vsphere-cloud-controller-manager-ffkp5     1/1     Running   0    26s

И в конце снимаем taint лейбл, который рапрещает шедуллить новые поды на ноды кластера. Для реализации следующего этапа обязательно нужно снять все метки:

# Untain all lables 
[root@k8s-node1 ~]# kubectl patch node  k8s-node1 -p '{"spec":{"taints":[]}}'
node/k8s-node1 patched
[root@k8s-node1 ~]# kubectl patch node  k8s-node2 -p '{"spec":{"taints":[]}}'
node/k8s-node2 patched
[root@k8s-node1 ~]# kubectl patch node  k8s-node3 -p '{"spec":{"taints":[]}}'
node/k8s-node3 patched

# Check current taints
[root@k8s-node1 ~]# kubectl describe nodes | egrep "Taints:|Name:"
Name:               k8s-node1
Taints:             <none>
Name:               k8s-node2
Taints:             <none>
Name:               k8s-node3
Taints:             <none>

Установка CSI

Как взаимодействуют объекты постоянных томов разбирали в заметке ранее. В разрезе vSphere, CSI драйвер генерирует запрос на постоянный том в формат понятный для API vCenter-сервера и отдает его. Далее vCenter создает новый vmdk-том на основании storage-политики и примонтирует его на виртуалки с запущенным k8s.

Создаем отдельный namespace для драйвера:

[root@k8s-node1 ~]# kubectl create ns vmware-system-csi

Теперь требуется написать конфигурационный файл, с помошью которого кластер будет коннектится к vCenter:

[root@k8s-node1 ~]# vim /etc/kubernetes/csi-vsphere.conf
---
[Global]
cluster-id = "k8s-vsphere"
cluster-distribution = "Vanilla"
#ca-file = <ca file path> # optional, use with insecure-flag set to false
#thumbprint = "<cert thumbprint>" # optional, use with insecure-flag set to false without providing ca-file

[VirtualCenter "vc01"]
insecure-flag = false
user = "k8s-cns-user@local"
password = "password"
port = "443"
datacenters = "Office"

Думаю исходя из имен переменных понятна их суть. В документации можно посмотреть дополнительно по конфиге.

Также отмечу, что для продакшена лучше начать использовать TLS, в моем примере опции ca-file и thumbprint закоментированы.

Отлично, теперь создаем новый секрет с указанием конфигурационного файла:

[root@k8s-node1 ~]# kubectl create secret generic vsphere-config-secret --from-file=/etc/kubernetes/csi-vsphere.conf -n vmware-system-csi
secret/vsphere-config-secret created

Применяем манифест с плагином:

[root@k8s-node1 ~]# kubectl apply -f https://raw.githubusercontent.com/kubernetes-sigs/vsphere-csi-driver/master/manifests/vanilla/vsphere-csi-driver.yaml

Смотрим, что поды с плагином поднялись:

[root@k8s-node1 ~]# kubectl get pod -n vmware-system-csi
NAME                                      READY   STATUS    RESTARTS      AGE
vsphere-csi-controller-6cf746566d-b8rpl   7/7     Running   0             42m
vsphere-csi-node-fjgt7                    3/3     Running   2 (38m ago)   42m
vsphere-csi-node-ll6dh                    3/3     Running   5 (38m ago)   42m
vsphere-csi-node-vd8wl                    3/3     Running   5 (39m ago)   42m

Создаем запрос на том

В завершение всех эпопеи, давайте убедимся что все работает корретно.

Создаем новый storage класс:

---
kind: StorageClass
apiVersion: storage.k8s.io/v1
metadata:
  name: vsphere-sc
  #annotations: storageclass.kubernetes.io/is-default-class=true
provisioner: csi.vsphere.vmware.com
parameters:
  storagepolicyname: "k8s-nvme-datastore"
  datastoreurl: "ds:///vmfs/volumes/sda15101-68asdg288b-364d-9uHg125e912b20/"

В поле с параметрами, указываем имя storage-политики, которую создали ранее:

storagepolicyname: "k8s-nvme-datastore"

А в значении datastoreurl прописываем url датастора, который можно найти в vSphere ui.

Далее создаем запрос на том:

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: vsphere-pvc
spec:
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 5Gi
  storageClassName: vsphere-sc

И применяем оба манифеста:

[root@k8s-node1 ~]# kubectl apply -f vsphere-sc.yml
[root@k8s-node1 ~]# kubectl apply -f vsphere-pvc.yml

Смотрим статус нашего запроса:

[root@k8s-node1 ~]# kubectl get pvc
NAME          STATUS   VOLUME                                     CAPACITY   ACCESS MODES   STORAGECLASS   AGE
vsphere-pvc   Bound    pvc-435376bf-5830-4e93-8ad5-756f70517164   5Gi        RWO            vsphere-sc     28m

Как можно понять из вывода он в статусе bound. Отлично!

Что бы убедится, что диск был создан идем в vSphere UI и находим созданный том: k8s-vsphere-st-result.png


Дополнительно почитать