【问题标题】:How can I mount the same persistent volume on multiple pods?如何在多个 pod 上挂载相同的持久卷?
【发布时间】:2020-09-30 05:35:58
【问题描述】:

我有一个三节点 GCE 集群和一个具有三个副本的单 Pod 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,但在任何给定时间只有一个 pod 可以挂载卷。其余的给出“错误 400:RESOURCE_IN_USE_BY_ANOTHER_RESOURCE”。我怎样才能让所有三个副本从同一个卷中读取相同的 Web 内容?

【问题讨论】:

    标签: kubernetes google-cloud-platform google-kubernetes-engine persistent-volumes persistent-volume-claims


    【解决方案1】:

    Google 提供类似 NFS 的文件系统,称为 Google Cloud Filestore。您可以将其安装在多个 pod 上。

    【讨论】:

      【解决方案2】:

      如果您想在不同节点之间共享卷并提供高度可扩展的解决方案,您将需要使用 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 个 Pod)之间交付共享静态内容,每月通过 Nginx 服务 10 亿个请求。它确实与 NFS 设置完美配合 :)

      【讨论】:

        【解决方案3】:

        首先,我想指出您的配置中的一个基本差异。请注意,当您使用您在示例中定义的 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 将用作您的卷配置器。你可以阅读更多关于它here:

        配置了 gcePersistentDisk 等卷实现 通过 StorageClass 资源。 GKE 为 您使用标准永久性磁盘类型 (ext4)。默认 当 PersistentVolumeClaim 未指定 存储类名。您可以替换提供的默认 StorageClass 用你自己的。

        但是让我们继续解决您面临的问题。

        解决办法:

        首先,我想强调您不必使用任何类似 NFS 的文件系统来实现您的目标

        如果您需要您的PersistentVolumeReadOnlyMany 模式下可用,GCE Persistent Disk 是完全满足您要求的完美解决方案。

        它可以同时被多个Podsro模式挂载,更重要的是被多个Pods调度在不同的GKEnodes上。此外,它的配置非常简单,并且可以在 GKE 上开箱即用。

        如果您想在ReadWriteMany 模式下使用您的存储,我同意像 NFS 这样的东西可能是唯一的解决方案,因为 GCE Persistent Disk 不提供这种功能。

        让我们仔细看看如何配置它。

        我们需要从定义我们的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,也只有一个 pod 可以 在任何给定时间挂载卷。

        嗯,实际上你没有。我知道这可能看起来有点棘手而且有点令人惊讶,但这并不是定义accessModes 真正起作用的方式。事实上,这是一个被广泛误解的概念。首先您不能在PVC 中定义访问模式,以将您想要的约束放在那里。支持的访问模式是特定存储类型的固有特性。它们已由存储提供程序定义。

        您在PVC 定义中实际做的是请求支持特定访问模式或访问模式的PV。请注意,它采用列表的形式,这意味着您可以提供许多您希望PV 支持的不同访问模式。

        基本上就像是在说:“嘿!存储提供商!给我一个支持ReadOnlyMany 模式的卷。”您以这种方式要求满足您要求的存储。但是请记住,您可以得到比您要求的更多的东西。这也是我们在 GCP 中请求支持ReadOnlyMany 模式的PV 时的场景。它为我们创建了一个PersistentVolume,它满足我们在accessModes 部分中列出的要求,但它也支持ReadWriteOnce 模式。虽然我们没有要求也支持ReadWriteOnce 的东西,但您可能会同意我的观点,内置支持这两种模式的存储完全满足我们对支持ReadOnlyMany 的东西的要求。所以基本上这就是它的工作方式。

        GCP 为响应您的 PVC 而自动配置的您的 PV 支持这两个 accessModes,如果您没有在 PodDeployment 定义中明确指定要安装它只读模式,默认挂载为读写模式。

        您可以通过附加到能够成功挂载PersistentVolumePod来轻松验证它:

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

        并试图在挂载的文件系统上写一些东西。

        你得到的错误信息:

        "Error 400: RESOURCE_IN_USE_BY_ANOTHER_RESOURCE"
        

        特别关注 GCE Persistent Disk,它已由一个 GKE nodeReadWriteOnce 模式下挂载,并且无法由另一个node 挂载Pods 的其余部分已安排。

        如果您希望它以ReadOnlyMany 模式挂载,则需要在Deployment 定义中明确指定它,方法是在Pod's 模板规范下的volumes 部分添加readOnly: true 语句,如下所示:

        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,该Pod 仅用于将已上传到我们的一个GKE 节点 的数据复制到我们的目标PV

        请注意,使用数据预填充PersistentVolume 可以通过多种不同方式完成。您可以仅将您将在Deployment 中使用的PersistentVolume 安装在Pod 中,并使用curlwget 从某个外部位置获取数据,将其直接保存在您的目的地PV。这取决于你。

        在我的示例中,我将展示如何使用额外的 local 卷来执行此操作,该卷允许我们挂载到 Poddirectorypartitiondisk(在我的示例中,我使用目录/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 Persistent Disk

        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 最终可以通过位于各个GKE 节点上的所有PodsreadOnly 模式挂载::

        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 Persistent Disk 并切换到上面提到的local 卷。这样,您所有的Pods 不仅可以读取它,还可以同时写入它。唯一需要注意的是,所有Pods 都将在单个节点上运行。

        【讨论】:

        • 这是一个很好的答案。谢谢!
        • 是否可以有一个带访问模式[ReadWriteOnce, ReadOnlyMany] 的 PVC,一个 pod 将其安装为 r/w,而其他 pod 将其安装为 r/o?
        【解决方案4】:

        您可以使用类似 NFS 的文件系统来实现此目的。在 Google Cloud 上,Filestore 是适合此目的的产品(NFS 托管)。你有一个教程here 来实现你的配置

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2023-03-08
          • 1970-01-01
          • 1970-01-01
          • 2018-06-10
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2019-11-12
          相关资源
          最近更新 更多