我们的情景:
我们使用ceph rbd存储一些机器学习训练数据集,工作流程如下:
使用AccessMode创建一个ceph-rbd pvc pvc-tranining-data:ReadWriteOnce。
使用1个pod创建一个写入作业来安装pvc-training-data并将训练数据写入pvc-training-data。
将训练数据写入pvc-training-data后,容器将退出,pvc-trainiing-data pvc将被k8s卸载,写入作业完成。
创建一个包含n个pod的读取作业,其中n >= 1
用readOnly: true
挂载pvc-training-data以使用训练数据。
顺便说一句:我们使用k8s 1.6.1
到目前为止,工作流程适用于我们的使用场景,但我对使用AccessMode的PVC AccessMode和ceph rbd有一些疑问:ReadWriteOnce。
如何理解AccessModes:ReadOnlyMany,ReadWriteOnce,ReadWriteMany? 我认为使用范围是ReadOnlyMany< ReadWriteOnce< ReadWriteMay,所以如果我使用AccessMode:ReadWriteOnce应用PVC,我可以使用它 它作为AccessMode:ReadOnlyMany PVC,我是对的吗?
- 醇>
ceph rbd是一个块设备,每个容器(在不同的主机上)挂载相同的ceph rbd设备都会有自己的文件系统,所以唯一允许的AccessMode是ReadOnlyMany或ReadWriteOnce,我们是否应该对ReadWriteOnce的使用施加限制k8s代码?
- 如果Pod使用readOnly:false安装了ReadWriteOnce pvc,那么在卸载之前,不能再安装Pod,直到卸载它为止。
- 如果一个Pod使用readOnly:true挂载ReadWriteOnce pvc,只要它们设置为readOnly:true,它就只能挂载到其他Pod。
- 对同一Pod中的容器没有限制,因为它们从主机$ {KUBELET_ROOT} / plugins / {xx} /
共享相同的文件系统 醇>
答案 0 :(得分:0)
关于你的第一个问题:
- 如何理解AccessModes:ReadOnlyMany,ReadWriteOnce, ReadWriteMany?我认为使用范围是ReadOnlyMany< ReadWriteOnce < ReadWriteMay,所以如果我使用AccessMode:ReadWriteOnce应用PVC,它 没问题我把它用作AccessMode:ReadOnlyMany PVC,我是对的吗?
醇>
docs明确指出:
Important! A volume can only be mounted using one access mode at a
time, even if it supports many.
我的第二个问题并不清楚。但我认为考虑到第一个问题的答案可能无效?
答案 1 :(得分:0)
要回答这个问题,看一下源代码可能会很有用。正如我在集群迁移中遇到的问题一样,情况是这样的: 我们有一个带v1.9的集群,一个PV带有accessMode:ReadWriteMany(RWX),PVC带有accessMode:ReadWriteOnce,这两个可以绑定而没有任何错误。
我们将一些应用程序迁移到新集群(v1.12),并且在新集群中,PVC绑定给出了错误:
无法绑定到请求的卷“卷名”:不兼容 accessMode
我在v1.12的源代码中搜索了错误,并且看到了以下几行:
v1.12:
//checkVolumeSatisfyClaim checks if the volume requested by the claim satisfies the requirements of the claim
func checkVolumeSatisfyClaim(volume *v1.PersistentVolume, claim *v1.PersistentVolumeClaim) error {
requestedQty := claim.Spec.Resources.Requests[v1.ResourceName(v1.ResourceStorage)]
requestedSize := requestedQty.Value()
// check if PV's DeletionTimeStamp is set, if so, return error.
if utilfeature.DefaultFeatureGate.Enabled(features.StorageObjectInUseProtection) {
if volume.ObjectMeta.DeletionTimestamp != nil {
return fmt.Errorf("the volume is marked for deletion")
}
}
volumeQty := volume.Spec.Capacity[v1.ResourceStorage]
volumeSize := volumeQty.Value()
if volumeSize < requestedSize {
return fmt.Errorf("requested PV is too small")
}
requestedClass := v1helper.GetPersistentVolumeClaimClass(claim)
if v1helper.GetPersistentVolumeClass(volume) != requestedClass {
return fmt.Errorf("storageClassName does not match")
}
isMisMatch, err := checkVolumeModeMisMatches(&claim.Spec, &volume.Spec)
if err != nil {
return fmt.Errorf("error checking volumeMode: %v", err)
}
if isMisMatch {
return fmt.Errorf("incompatible volumeMode")
}
if !checkAccessModes(claim, volume) {
return fmt.Errorf("incompatible accessMode")
}
return nil
}
// Returns true if PV satisfies all the PVC's requested AccessModes
func checkAccessModes(claim *v1.PersistentVolumeClaim, volume *v1.PersistentVolume) bool {
pvModesMap := map[v1.PersistentVolumeAccessMode]bool{}
for _, mode := range volume.Spec.AccessModes {
pvModesMap[mode] = true
}
for _, mode := range claim.Spec.AccessModes {
_, ok := pvModesMap[mode]
if !ok {
return false
}
}
return true
}
v1.9:
//checkVolumeSatisfyClaim checks if the volume requested by the claim satisfies the requirements of the claim
func checkVolumeSatisfyClaim(volume *v1.PersistentVolume, claim *v1.PersistentVolumeClaim) error {
requestedQty := claim.Spec.Resources.Requests[v1.ResourceName(v1.ResourceStorage)]
requestedSize := requestedQty.Value()
isMisMatch, err := checkVolumeModeMisMatches(&claim.Spec, &volume.Spec)
if err != nil {
return fmt.Errorf("error checking if volumeMode was a mismatch: %v", err)
}
volumeQty := volume.Spec.Capacity[v1.ResourceStorage]
volumeSize := volumeQty.Value()
if volumeSize < requestedSize {
return fmt.Errorf("Storage capacity of volume[%s] requested by claim[%v] is not enough", volume.Name, claimToClaimKey(claim))
}
requestedClass := v1helper.GetPersistentVolumeClaimClass(claim)
if v1helper.GetPersistentVolumeClass(volume) != requestedClass {
return fmt.Errorf("Class of volume[%s] is not the same as claim[%v]", volume.Name, claimToClaimKey(claim))
}
if isMisMatch {
return fmt.Errorf("VolumeMode[%v] of volume[%s] is incompatible with VolumeMode[%v] of claim[%v]", volume.Spec.VolumeMode, volume.Name, claim.Spec.VolumeMode, claim.Name)
}
return nil
}
// checkVolumeModeMatches is a convenience method that checks volumeMode for PersistentVolume
// and PersistentVolumeClaims along with making sure that the Alpha feature gate BlockVolume is
// enabled.
// This is Alpha and could change in the future.
func checkVolumeModeMisMatches(pvcSpec *v1.PersistentVolumeClaimSpec, pvSpec *v1.PersistentVolumeSpec) (bool, error) {
if utilfeature.DefaultFeatureGate.Enabled(features.BlockVolume) {
if pvSpec.VolumeMode != nil && pvcSpec.VolumeMode != nil {
requestedVolumeMode := *pvcSpec.VolumeMode
pvVolumeMode := *pvSpec.VolumeMode
return requestedVolumeMode != pvVolumeMode, nil
} else {
// This also should retrun an error, this means that
// the defaulting has failed.
return true, fmt.Errorf("api defaulting for volumeMode failed")
}
} else {
// feature gate is disabled
return false, nil
}
}
当您查看 checkAccessModes 函数中的v1.12代码时,它会将卷accessModes放入 Map 中,如果不能,则在该映射中搜索PVC accessModes。在该地图中找到PVC accessMode,它将返回false,这会导致不兼容的accessMode 错误。
那么为什么我们在v1.9中没有收到此错误?因为它在 checkVolumeModeMisMatches 函数中具有不同的控件。 它检查默认情况下为假的Alpha功能,称为 BlockVolume 。因为它是假的,所以不会遇到
if isMisMatch {
return fmt.Errorf("VolumeMode[%v] of volume[%s] is incompatible with VolumeMode[%v] of claim[%v]", volume.Spec.VolumeMode, volume.Name, claim.Spec.VolumeMode, claim.Name)
}
代码块。
您可以使用以下命令检查主节点上的BlockVolume功能:
ps aux | grep apiserver | grep feature-gates
我希望它能澄清您的问题。尤其是v1.12中的 checkAccessModes 函数也在PV-PVC accessMode控件的当前master分支中。