Как я могу смонтировать один и тот же постоянный том на нескольких модулях?

У меня есть кластер GCE с тремя узлами и развертывание GKE с одним модулем с тремя репликами. Я создал PV и PVC так:

# Create a persistent volume for web content
apiVersion: v1
kind: PersistentVolume
metadata:
  name: nginx-content
  labels:
    type: local
spec:
  capacity:
    storage: 5Gi
  accessModes:
   - ReadOnlyMany
  hostPath:
    path: "/usr/share/nginx/html"
--
# Request a persistent volume for web content
kind: PersistentVolumeClaim
apiVersion: v1
metadata:
  name: nginx-content-claim
  annotations:
    volume.alpha.kubernetes.io/storage-class: default
spec:
  accessModes: [ReadOnlyMany]
  resources:
    requests:
      storage: 5Gi

На них ссылаются в спецификации контейнера так:

    spec:
      containers:
      - image: launcher.gcr.io/google/nginx1
        name: nginx-container
        volumeMounts:
          - name: nginx-content
            mountPath: /usr/share/nginx/html
        ports:
          - containerPort: 80
      volumes:
      - name: nginx-content
        persistentVolumeClaim:
          claimName: nginx-content-claim

Несмотря на то, что я создал тома как ReadOnlyMany, только один модуль может монтировать том в любой момент времени. Остальные выдают "Ошибка 400: RESOURCE_IN_USE_BY_ANOTHER_RESOURCE". Как сделать так, чтобы все три реплики читали один и тот же веб-контент с одного тома?




Ответы (4)


Сначала я хотел бы указать на одно принципиальное несоответствие в вашей конфигурации. Обратите внимание, что когда вы используете свой PersistentVolumeClaim, определенный как в вашем примере, вы вообще не используете свой nginx-content PersistentVolume. Вы можете легко проверить это, запустив:

kubectl get pv

в вашем кластере GKE. Вы заметите, что помимо вашего nginx-content PV, созданного вручную, есть еще один, который был автоматически подготовлен на основе примененного PVC.

Обратите внимание, что в вашем PersistentVolumeClaim определении вы явно ссылаетесь на класс хранилища default, который не имеет ничего общего с вашим вручную созданным PV. Собственно, даже если полностью опустить аннотацию:

annotations:
        volume.alpha.kubernetes.io/storage-class: default

он будет работать точно так же, а именно класс хранения default будет использоваться в любом случае. Использование класса хранилища по умолчанию для GKE означает, что GCE Persistent Disk будет использоваться в качестве вашего тома. Подробнее об этом можно узнать здесь:

Реализации томов, такие как gcePersistentDisk, настраиваются с помощью ресурсов StorageClass. GKE создает для вас StorageClass по умолчанию, который использует стандартный постоянный тип диска (ext4). StorageClass по умолчанию используется, когда PersistentVolumeClaim не указывает StorageClassName. Вы можете заменить предоставленный StorageClass по умолчанию своим собственным.

Но перейдем к решению стоящей перед вами проблемы.

Решение:

Во-первых, я хотел бы подчеркнуть, вам не нужно использовать какие-либо файловые системы, подобные NFS, для достижения вашей цели.

Если вам нужно, чтобы ваш PersistentVolume был доступен в ReadOnlyMany режиме, Постоянный диск GCE - идеальное решение, полностью отвечающее вашим требованиям.

Он может быть установлен в ro режиме многими Pods одновременно и, что еще важнее, многими Pods, запланированными на разных GKE nodes. Кроме того, его очень просто настроить, и он работает на GKE из коробки.

Если вы хотите использовать свое хранилище в ReadWriteMany режиме, я согласен, что что-то вроде NFS может быть единственным решением, поскольку Постоянный диск GCE не предоставляет такой возможности.

Давайте подробнее рассмотрим, как мы можем его настроить.

Нам нужно начать с определения нашего PVC. Этот шаг на самом деле уже был сделан вами, но вы немного заблудились в дальнейших шагах. Позвольте мне объяснить, как это работает.

Следующая конфигурация верна (как я уже упоминал, раздел annotations можно опустить):

# Request a persistent volume for web content
kind: PersistentVolumeClaim
apiVersion: v1
metadata:
  name: nginx-content-claim
spec:
  accessModes: [ReadOnlyMany]
  resources:
    requests:
      storage: 5Gi

Однако я хотел бы добавить к этому один важный комментарий. Вы сказали:

Несмотря на то, что я создал тома как ReadOnlyMany, только один модуль может монтировать том в любой момент времени.

Ну, на самом деле нет. Я знаю, что это может показаться немного сложным и несколько удивительным, но определение accessModes работает не так. На самом деле это широко неправильно понимаемая концепция. Во-первых, вы не можете определять режимы доступа в PVC в том смысле, что устанавливаете там нужные вам ограничения. Поддерживаемые режимы доступа присущи определенному типу хранилища. Они уже определены поставщиком хранилища.

Фактически в PVC определении вы запрашиваете PV, который поддерживает конкретный режим доступа или режимы доступа. Обратите внимание, что он представлен в виде списка, что означает, что вы можете предоставить множество различных режимов доступа, которые должен поддерживать ваш PV.

По сути, это как сказать: Эй! Провайдер хранилища! Дайте мне том, который поддерживает режим ReadOnlyMany. Вы запрашиваете таким образом хранилище, которое будет удовлетворять вашим требованиям. Однако имейте в виду, что вам могут дать больше, чем вы просите. И это также наш сценарий, когда мы запрашиваем PV, который поддерживает ReadOnlyMany режим в GCP. Он создает для нас PersistentVolume, который соответствует нашим требованиям, перечисленным в разделе accessModes, но также поддерживает режим ReadWriteOnce. Хотя мы не просили что-то, что также поддерживает ReadWriteOnce, вы, вероятно, согласитесь со мной, что хранилище, которое имеет встроенную поддержку этих двух режимов, полностью удовлетворяет наш запрос на что-то, что поддерживает ReadOnlyMany. Так в основном это работает.

Ваш PV, который был автоматически предоставлен GCP в ответ для вашего PVC, поддерживает эти два accessModes, и если вы явно не укажете в Pod или Deployment определении, что хотите подключить его в режиме только для чтения, по умолчанию он монтируется в режиме чтения-записи.

Вы можете легко проверить это, подключив Pod, который смог успешно смонтировать PersistentVolume:

kubectl exec -ti pod-name -- /bin/bash

и пытается что-то записать в смонтированной файловой системе.

Вы получаете сообщение об ошибке:

"Error 400: RESOURCE_IN_USE_BY_ANOTHER_RESOURCE"

касается, в частности, постоянного диска GCE, который уже смонтирован одним GKE node в ReadWriteOnce режиме и не может быть смонтирован другим node, на котором были запланированы остальные Pods.

Если вы хотите, чтобы он был смонтирован в режиме ReadOnlyMany, вам необходимо явно указать это в своем Deployment определении, добавив оператор readOnly: true в раздел volumes под спецификацией шаблона Pod's, как показано ниже:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-deployment
  labels:
    app: nginx
spec:
  replicas: 3
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx:1.14.2
        ports:
        - containerPort: 80
        volumeMounts:
        - mountPath: "/usr/share/nginx/html"
          name: nginx-content
      volumes:
      - name: nginx-content
        persistentVolumeClaim:
          claimName: nginx-content-claim
          readOnly: true

Однако имейте в виду, что для того, чтобы смонтировать его в режиме readOnly, сначала нам нужно предварительно заполнить такой том данными. В противном случае вы увидите другое сообщение об ошибке, в котором говорится, что неформатированный том нельзя смонтировать в режиме только для чтения.

Самый простой способ сделать это - создать единый Pod, который будет служить только для копирования данных, которые уже были загружены на один из наших узлов GKE, в пункт назначения PV.

Обратите внимание, что предварительное заполнение PersistentVolume данными может выполняться разными способами. Вы можете смонтировать в таком Pod только свой PersistentVolume, который вы будете использовать в своем Deployment, и получить свои данные с помощью curl или wget из некоторого внешнего местоположения, сохраняя их прямо в пункте назначения PV. Тебе решать.

В моем примере я показываю, как это сделать, используя дополнительный локальный том, который позволяет нам подключаться к нашим Pod a directory, partition или disk (в моем примере я использую каталог /var/tmp/test, расположенный на одном из моих узлов GKE), доступный на одном из наших узлов kubernetes. Это гораздо более гибкое решение, чем hostPath, поскольку нам не нужно заботиться о планировании таких Pod для конкретного узла, который содержит данные. Конкретное правило сходства узлов уже определено в PersistentVolume, а Pod автоматически назначается для конкретного узла.

Для его создания нам понадобятся 3 вещи:

StorageClass:

apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: local-storage
provisioner: kubernetes.io/no-provisioner
volumeBindingMode: WaitForFirstConsumer

PersistentVolume определение:

apiVersion: v1
kind: PersistentVolume
metadata:
  name: example-pv
spec:
  capacity:
    storage: 10Gi
  volumeMode: Filesystem
  accessModes:
  - ReadWriteOnce
  persistentVolumeReclaimPolicy: Delete
  storageClassName: local-storage
  local:
    path: /var/tmp/test
  nodeAffinity:
    required:
      nodeSelectorTerms:
      - matchExpressions:
        - key: kubernetes.io/hostname
          operator: In
          values:
          - <gke-node-name>

и наконец PersistentVolumeClaim:

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: myclaim
spec:
  accessModes:
    - ReadWriteOnce
  volumeMode: Filesystem
  resources:
    requests:
      storage: 10Gi
  storageClassName: local-storage

Затем мы можем создать временный Pod, который будет служить только для копирования данных с нашего узла GKE на наш постоянный диск GCE.

apiVersion: v1
kind: Pod
metadata:
  name: mypod
spec:
  containers:
    - name: myfrontend
      image: nginx
      volumeMounts:
      - mountPath: "/mnt/source"
        name: mypd
      - mountPath: "/mnt/destination"
        name: nginx-content
  volumes:
    - name: mypd
      persistentVolumeClaim:
        claimName: myclaim
    - name: nginx-content
      persistentVolumeClaim:
        claimName: nginx-content-claim

Пути, которые вы видите выше, на самом деле не важны. Задача этого Pod состоит только в том, чтобы позволить нам копировать наши данные в пункт назначения PV. В конце концов, наш PV будет смонтирован совершенно по другому пути.

После того, как Pod создан и оба тома успешно смонтированы, мы можем подключиться к нему, запустив:

kubectl exec -ti my-pod -- /bin/bash

С Pod просто запустите:

cp /mnt/source/* /mnt/destination/

Это все. Теперь мы можем exit и удалить наши временные Pod:

kubectl delete pod mypod

Как только он исчезнет, ​​мы можем применить наш Deployment и наш PersistentVolume, наконец, можно будет смонтировать в readOnly режиме всеми Pods, расположенными на различных узлах GKE:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-deployment
  labels:
    app: nginx
spec:
  replicas: 3
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx:1.14.2
        ports:
        - containerPort: 80
        volumeMounts:
        - mountPath: "/usr/share/nginx/html"
          name: nginx-content
      volumes:
      - name: nginx-content
        persistentVolumeClaim:
          claimName: nginx-content-claim
          readOnly: true

Кстати. если вас устраивает тот факт, что ваш Pods будет запланирован только на одном конкретном узле, вы можете вообще отказаться от использования постоянного диска GCE и переключиться на вышеупомянутый локальный том. Таким образом, весь ваш Pods сможет не только читать с него, но и писать в него одновременно. Единственное предостережение - все эти Pods будут работать на одном узле.

person mario    schedule 23.06.2020
comment
И это отличный ответ. Спасибо! - person Observer; 03.06.2021

Вы можете добиться этого с помощью файловой системы, подобной NFS. В Google Cloud для этого подходит Filestore (управляемый NFS). У вас есть руководство здесь по настройке конфигурации.

person guillaume blaquiere    schedule 10.06.2020

Вам нужно будет использовать утверждение общего тома с типом ReadWriteMany (RWX), если вы хотите разделить том между разными узлами и предоставить хорошо масштабируемое решение. Как при использовании сервера NFS.

Вы можете узнать, как развернуть сервер NFS здесь:

https://www.shebanglabs.io/run-nfs-server-on-ubuntu-20-04/

Затем вы можете монтировать тома (каталоги с сервера NFS) следующим образом:

https://www.shebanglabs.io/how-to-set-up-read-write-many-rwx-persistent-volumes-with-nfs-on-kubernetes/

Я использовал такой способ доставки общего статического контента между развертываниями +8 k8s (+200 подов), обслуживая 1 миллиард запросов в месяц через Nginx. и он отлично работал с этой настройкой NFS :)

person Tarek N. Elsamni    schedule 26.06.2020

Google предоставляет файловую систему, подобную NFS, которая называется Google Cloud Filestore. Вы можете установить его на несколько модулей.

person Nikhil    schedule 27.10.2020