首先,我想指出您的配置中的一个基本差异。请注意,当您使用您在示例中定义的 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 的文件系统来实现您的目标。
如果您需要您的PersistentVolume 在ReadOnlyMany 模式下可用,GCE Persistent Disk 是完全满足您要求的完美解决方案。
它可以同时被多个Pods以ro模式挂载,更重要的是被多个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,如果您没有在 Pod 或 Deployment 定义中明确指定要安装它只读模式,默认挂载为读写模式。
您可以通过附加到能够成功挂载PersistentVolume的Pod来轻松验证它:
kubectl exec -ti pod-name -- /bin/bash
并试图在挂载的文件系统上写一些东西。
你得到的错误信息:
"Error 400: RESOURCE_IN_USE_BY_ANOTHER_RESOURCE"
特别关注 GCE Persistent Disk,它已由一个 GKE node 在ReadWriteOnce 模式下挂载,并且无法由另一个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 中,并使用curl 或wget 从某个外部位置获取数据,将其直接保存在您的目的地PV。这取决于你。
在我的示例中,我将展示如何使用额外的 local 卷来执行此操作,该卷允许我们挂载到 Pod 和 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 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 节点上的所有Pods 以readOnly 模式挂载::
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 都将在单个节点上运行。