Як організувати доступ до файлової системи хосту для Persistent Volume в Kind

💡 Усі статті, обговорення, новини про DevOps — в одному місці. Приєднуйтесь до DevOps спільноти!

Кубернетіс вже перестає бути чимось таким, з чим працюють лише інженери платформи. Все більше і більше застосунків загортаються в контейнери та запускаються в контейнерних середовищах. Що робити, якщо з певних причин у вас немає доступу до хмарної платформи, але треба вести розробку застосунку, який працюватиме в хмарі? Ви можете скористатись Kind для локального розгортання Кубернетіс.

Kind — це інструмент для запуску локальних кластерів Kubernetes з використанням «вузлів» контейнерів Docker. В першу чергу kind був розроблений для тестування самого Kubernetes, але може бути використаний для локальної розробки або CI.

Для запуску Kind вам знадобиться docker, podman або інший рушій для роботи з контейнерами. Ви можете звернутись до Швидкого початку роботи з Kind на офіційному сайті.

Створення кластера

Отже, ми маємо встановлений Kind та середовище для роботи з контейнерами, kubectl — інструмент командного рядка для виконання маніпуляцій з кластером.

Для створення локального кластера скористаємось командою kind create cluster.

kind create cluster

Ви завжди можете отримати потрібну довідку, скориставшись командою виду kind [command] --help.

kind --help

Ми створили свій локальний кластер з іменем kind-kind. Це стандартне імʼя, яке використовує kind, якщо параметр -n назва або --name назва не вказано.

Крім використання ключів для kind ми можемо використати маніфест для створення кластера з потрібними нам параметрами.

cat <<EOF > kind-config.yaml
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
name: my-super-cluster
nodes:
- role: control-plane
- role: worker
  extraMounts:
  - hostPath: /path/to/local/data
    containerPath: /data
# - role: worker
# - role: worker
#   extraMounts:
#   - hostPath: /path/to/local/data/dump
#     containerPath: /data/dump
#   - hostPath: /path/to/local/data/diff
#     containerPath: /data/diff

☝️ тут ми можемо зазначити кількість потрібних вузлів, їх роль та, головне, в нашому випадку, — шлях у локальній файловій системі, який ми будемо монтувати у вузли нашого кластера та використовувати як систему зберігання для наших Постійних Томів (Persistent Volumes). Див Extra Mounts в документації Kind.

Застосуємо нашу конфігурацію для створення кластера

kind create cluster --config kind-config.yaml
Creating cluster "kind" ...
 ✓ Ensuring node image (kindest/node:v1.32.2) 🖼
 ✓ Preparing nodes 📦
 ✓ Writing configuration 📜
 ✓ Starting control-plane 🕹️
 ✓ Installing CNI 🔌
 ✓ Installing StorageClass 💾
Set kubectl context to "kind-my-super-cluster"
You can now use your cluster with:

kubectl cluster-info --context kind-my-super-cluster

Have a question, bug, or feature request? Let us know! https://kind.sigs.k8s.io/#community 🙂

Перевіримо, що файлову систему хосту змонтовано у вузол worker нашого кластера.

docker container inspect osm-cluster-worker \
  | jq '[{"Name": .[0].Name,
          "BindMounts": (
            .[] |
            .Mounts[] |
            select(.Type == "bind")
        )}]'

І бачимо, що все ОК, файлову систему змонтовано.

[
  {
    "Name": "/my-super-cluster-worker",
    "BindMounts": {
      "Type": "bind",
      "Source": "/host_mnt/path/to/local/data",
      "Destination": "/data",
      "Mode": "",
      "RW": true,
      "Propagation": "rprivate"
    }
  },
  {
    "Name": "/my-super-cluster-worker",
    "BindMounts": {
      "Type": "bind",
      "Source": "/lib/modules",
      "Destination": "/lib/modules",
      "Mode": "ro",
      "RW": false,
      "Propagation": "rprivate"
    }
  }
]

Створення PersistentVolume та PersistentVolumeClaim

Створимо маніфест для Постійного Тому:

apiVersion: v1
kind: PersistentVolume
metadata:
  name: my-super-cluster-pv
spec:
  capacity:
    storage: 100Gi
  accessModes:
    - ReadWriteOnce
  volumeMode: Filesystem
  hostPath:
    path: "/data"
  storageClassName: my-storageclass

А також створимо Заявку PersistentVolumeClaim, яку будемо використовувати для монтування Постійного Тому в робочі навантаження.

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name:  my-super-cluster-pvc
spec:
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 100Gi
  storageClassName: my-storageclass

Тепер найголовніше 🥁, потрібно створити StorageClass, який дозволить нам явно повʼязати Заявку PVC з Постійним Томом PV.

Примітка: зверніть увагу, що kind створює стандартний StorageClass під час створення кластера. Однак цей StorageClass не задовольняє наші вимоги, маючи reclaimPolicy:Delete.

kubectl get storageclass
NAME                 PROVISIONER             RECLAIMPOLICY   VOLUMEBINDINGMODE      ALLOWVOLUMEEXPANSION   AGE
standard (default)   rancher.io/local-path   Delete          WaitForFirstConsumer   false                  80m

Це означає, що вміст нашого Постійного Тому буде очищатись після його розмонтування з пода, а це не те, що нам треба.

Створимо наш StorageClass в кластері.

kubectl apply -f -  <<EOF
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: my-storageclass
provisioner: rancher.io/local-path
parameters:
  nodePath: /data
reclaimPolicy: Retain
volumeBindingMode: WaitForFirstConsumer
EOF
storageclass.storage.k8s.io/my-storageclass created

Перевіримо наш StorageClass.

kubectl get storageclass
NAME                 PROVISIONER             RECLAIMPOLICY   VOLUMEBINDINGMODE      ALLOWVOLUMEEXPANSION   AGE
my-storageclass      rancher.io/local-path   Retain          WaitForFirstConsumer   false                  5m27s
standard (default)   rancher.io/local-path   Delete          WaitForFirstConsumer   false                  91m

І зробимо його типовим, на всяк випадок:

kubectl patch storageclass my-storageclass -p '{"metadata": {"annotations":{"storageclass.kubernetes.io/is-default-class":"true"}}}'

А StorageClass standard навпаки зробимо звичайним.

kubectl patch storageclass standard -p '{"metadata": {"annotations":{"storageclass.kubernetes.io/is-default-class":"false"}}}'

Переглянемо поточні відомості про StorageClass:

kubectl get storageclass
NAME                        PROVISIONER             RECLAIMPOLICY   VOLUMEBINDINGMODE      ALLOWVOLUMEEXPANSION   AGE
my-storageclass (default)   rancher.io/local-path   Retain          WaitForFirstConsumer   false                  12m
standard                    rancher.io/local-path   Delete          WaitForFirstConsumer   false                  98m

Використання PersistentVolumeClaim в поді

Застосуємо маніфести PV та PVC в кластері.

kubectl apply -f pv.yaml -f pvc.yaml

Створимо под, який використовує Заявку на постійний том для зберігання даних.

kubectl apply -f - <<EOF
apiVersion: v1
kind: Pod
metadata:
  name: debug-pod
spec:
  containers:
  - name: debug-container
    image: busybox:latest
    command: ["sh", "-c", "sleep 3600"]
    volumeMounts:
    - mountPath: "/data"
      name: my-super-cluster
  volumes:
  - name: my-super-cluster
    persistentVolumeClaim:
      claimName: my-super-cluster-pvc
EOF

Перевіримо, що Заявка PVC має привʼязку до PV та використовується в нашому тестовому поді.

kubectl get pv
NAME                  CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS   CLAIM                          STORAGECLASS      VOLUMEATTRIBUTESCLASS   REASON   AGE
my-super-cluster-pv   100Gi      RWO            Retain           Bound    default/my-super-cluster-pvc   my-storageclass   <unset>                          9m20s
kubectl get pvc
NAME                   STATUS   VOLUME                CAPACITY   ACCESS MODES   STORAGECLASS      VOLUMEATTRIBUTESCLASS   AGE
my-super-cluster-pvc   Bound    my-super-cluster-pv   100Gi      RWO            my-storageclass   <unset>                 8m48s

Зверніть увагу, що статус PV та PVC має значення Bound, що означає, що Заявку було успішно звʼязано з Постійним Томом.

kubectl describe pod
Name:             debug-pod
Namespace:        default
Priority:         0
Service Account:  default
Node:             kind-control-plane/172.20.0.4
Start Time:       Fri, 04 Apr 2025 18:17:09 +0300
Labels:           <none>
Annotations:      <none>
Status:           Running
IP:               10.244.0.5
IPs:
  IP:  10.244.0.5
Containers:
  debug-container:
    Container ID:  containerd://d030a6edfc13c314853f22efc505990bbbb8e3954ed1c9887b9c7b3be575a0be

Image:         busybox:latest
    Image ID:      docker.io/library/busybox@sha256:37f7b378a29ceb4c551b1b5582e27747b855bbfaa73fa11914fe0df028dc581f
    Port:          <none>
    Host Port:     <none>
    Command:
      sh
      -c
      sleep 3600
    State:          Running
      Started:      Fri, 04 Apr 2025 18:17:13 +0300
    Ready:          True
    Restart Count:  0
    Environment:    <none>
    Mounts:
      /data from my-super-cluster (rw)
      /var/run/secrets/kubernetes.io/serviceaccount from kube-api-access-5wdzj (ro)
Conditions:
  Type                        Status
  PodReadyToStartContainers   True
  Initialized                 True
  Ready                       True
  ContainersReady             True
  PodScheduled                True
Volumes:
  my-super-cluster:
    Type:       PersistentVolumeClaim (a reference to a PersistentVolumeClaim in the same namespace)
    ClaimName:  my-super-cluster-pvc
    ReadOnly:   false
  kube-api-access-5wdzj:
    Type:                    Projected (a volume that contains injected data from multiple sources)
    TokenExpirationSeconds:  3607
    ConfigMapName:           kube-root-ca.crt
    ConfigMapOptional:       <nil>
    DownwardAPI:             true
QoS Class:                   BestEffort
Node-Selectors:              <none>
Tolerations:                 node.kubernetes.io/not-ready:NoExecute op=Exists for 300s
                             node.kubernetes.io/unreachable:NoExecute op=Exists for 300s
Events:
  Type    Reason     Age    From               Message
  ----    ------     ----   ----               -------
  Normal  Scheduled  8m36s  default-scheduler  Successfully assigned default/debug-pod to kind-control-plane
  Normal  Pulling    8m36s  kubelet            Pulling image "busybox:latest"
  Normal  Pulled     8m32s  kubelet            Successfully pulled image "busybox:latest" in 3.395s (3.395s including waiting). Image size: 1855985 bytes.
  Normal  Created    8m32s  kubelet            Created container: debug-container
  Normal  Started    8m32s  kubelet            Started container debug-container

Наш под було успішно створено і він працює.

Отримаємо доступ до термінала в нашому поді та перевіримо, що том змонтований у нашій файловій системі і все працює належним чином.

kubectl exec -it debug-pod -- sh
/ # ls -l / | grep data
drwxr-xr-x    2 root     root          4096 Apr  4 15:17 data
/ # touch /data/somefile.txt
/ # ls -l /data
total 0
-rw-r--r--    1 root     root             0 Apr  4 15:31 somefile.txt
/ #
/ # exit

Тепер перегляньте файлову систему хосту, змонтовану у вузол worker нашого кластера і ви побачите там тільки що створений файл somefile.txt.

Підсумки

Ми створили Заявку на використання Постійного Тому в робочому навантажені, яка використовує Клас Зберігання (StorageClass) для звʼязування Заявки з Томом. Постійний том використовує систему зберігання наявну на вузлі нашого кластера. Система зберігання вузла кластера базується на файловій системі хосту, на якому розгорнуто наш кластер.

У такий спосіб ми можемо надійно зберігати та повторно використовувати дані, розміщені в Постійному Томі в робочих навантаження нашого кластера, які за своєю природою мають обмежений життєвий цикл. Окрім цього ми також можемо передавати в наші робочі навантаження попередньо створені дані з нашого хосту та використовувати їх в подах.

Очищення

Для очищення (вилучення) кластера скористайтесь наступною командою.

kind delete cluster --name kind-my-super-cluster
Deleting cluster "kind-my-super-cluster" ...

Зачекайте, допоки Kind видалить кластер. За потреби видаліть створені файли у файловій системі хосту.

P.S.: Я також підтримую переклад документації Kubernetes і OpenTelemetry українською. Якщо хочете допомогти з перекладом та долучитися до проєкту — переходьте за посиланням. До речі, українська версія документацій Helm та Istio вже доступна на офіційних сайтах проєктів.

Додаткові матеріали

👍ПодобаєтьсяСподобалось6
До обраногоВ обраному3
LinkedIn
Дозволені теги: blockquote, a, pre, code, ul, ol, li, b, i, del.
Ctrl + Enter
Дозволені теги: blockquote, a, pre, code, ul, ol, li, b, i, del.
Ctrl + Enter

при роботі з kind ще буде корисним монтувати директорій containerd на свою хост машину.
Тоді, не доведеться скачувати докер іміджі кожного разу коли ви рестартуєте кластер, що значно скорочує час розгортання. Щось типу такого:

....
  extraMounts:
  - containerPath: /var/lib/containerd
    hostPath: /home/user/.kind/cache/containerd
....
Ну і ще, потрібно мати відповідну pullPolicy для свого деплоймента.
  pullPolicy: "IfNotPresent"

Цікава стаття. Будь ласка продовжуйте, якщо є матеріал та натхнення!
Дякую

а якщо ще LoadBalancer (балансувальники навантажень?) докинути kind.sigs.k8s.io/docs/user/loadbalancer буде пісня :)

Про це мабуть наступного разу.

Як організувати доступ до файлової системи хосту для Persistent Volume в Kind

В принципі, погана ідея, як на мене.

Підписатись на коментарі