1.PV和PVC
什么是PV和PVC?
- PV(PersistentVolume,持久卷),定义一个可以使用的数据卷,供K8S中的各个Pod使用,可以根据网络文件系统、云存储、Ceph存储等去作为持久卷的来源,比如我可以接入阿里云存储并封装成为PV,也可以接入NFS作为持久卷封装成为PV。
- PVC(PersistentVolumeClaim,持久卷声明),K8S集群根据现有的PV情况尝试去申请PV,会从众多PV当中选择出来一个合适的PV进行绑定。
为什么要有PV存在?因为容器本身是短暂的,一旦容器被删除,容器中的数据也会丢失。PV 提供了持久化存储,使得数据能够在容器删除、重启或迁移时得以保存。这对于大多数应用(如数据库、日志存储等)来说至关重要,因为它们需要持久的存储来存储数据。
PVC持久卷申请提交后,会从后端剩余的所有的可绑定的PV当中,根据预期的条件,选出来一个合适的PV进行最终的绑定。PVC和PV进行绑定的过程中,分为"预选"和"优选"两个阶段。
-
预选主要匹配下面几项:匹配容量,访问模式(RWO/RWX/ROX)和StorageClass。
- 容量匹配:选择PV预期容量>=PV容量条件满足的PV。比如业务方预期要10G的存储空间,现在有三个PV分别是5G,15G,30G,那么15G和30G满足我们的要求,5G的不满足我们的要求淘汰。
- 访问模式匹配:要求PVC的访问模式必须和PV的访问模式完全一致。RWO意味着PV只能挂载给一个Pod进行读写,RWX代表PV可以挂载给多个Pod进行读写,ROX则代表PV可以挂载给多个节点进行读取,不能进行写入。
- 比如业务方希望要一个RWX的PV,但是你给我一个RWO的PV,当然是不满足业务方的要求的。
- StorageClass匹配:不同的StorageClass意味着底层可能是完全不同的存储介质,比如其中一个StorageClass是SSD,另外一个StorageClass是HDD,两者的特性是完全不一样的。如果不对StorageClass进行精确匹配则会出现预期的存储介质不符合业务方的要求,因此是必须要求PVC和PV对应的StorageClass完全一致的。
-
优选:在预选阶段根据PVC的要求已经筛选出来所有满足条件的PV,但是这个时候可能存在有多个PV都可以用,比如我希望要10G,现在有一个10G的PV、一个15G的PV,还有一个30G的PV,此时PVC应该尝试去选取一个最优的PV尽量减少存储资源的浪费的情况。
1.1 创建持久卷
需要注意的是:持久卷是K8S集群维度的,不是namespace维度的,因此创建时不需要指定namespace。
我们可以基于如下的K8S资源清单去创建一个持久卷,使用NFS的方式去作为持久卷。
# pv.yaml
apiVersion: v1
kind: PersistentVolume
metadata:
name: nfs-pv
spec:
capacity:
storage: 5Gi # 根据需要调整存储大小
accessModes:
- ReadWriteMany # NFS 通常使用 ReadWriteMany 访问模式
nfs:
path: /sharedata/nfs # NFS 服务器上共享的目录路径
server: ... # NFS 服务器的 IP 地址
1.2 创建持久卷声明PVC
需要注意的是:PVC是namespace级别的,因此在声明时,需要明确指定namespace。
我们使用如下的K8S资源清单去创建PVC,在创建时,PVC会自动去匹配所有的PV持久卷,去匹配到合适的PV完成绑定。
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: nfs-pvc
namespace: wanna-project
spec:
accessModes:
- ReadWriteMany # 确保与 PV 的访问模式匹配
resources:
requests:
storage: 5Gi # 请求的存储大小
2.StorageClass
StorageClass(存储类),是一类存储的抽象,比如阿里云存储,AWS云存储,NFS,都可以作为一类StorageClass。
2.1 为什么要有StorageClass
想象以下场景:
- (1)我们想要1.5G的持久卷,但是目前没有能完全匹配1.2G的持久卷,只有1G/2G/5G/10G/...的容量的持久卷,如果我们想要使用PVC去申请持久卷时,就必须申请一个2G的持久卷,就会造成0.5GB的存储空间浪费。
- (2)我们每次想要去使用持久卷时,都得先创建PV,再去创建PVC。公司的PV一般是由公司的存储相关的运维工程师维护的,PVC则一般是后端的开发过程师去进行申请的,此时就会涉及到大量的跨部门的无效沟通,运维工程师还得去找到存储空间并封装成为PV给业务方使用,涉及到众多繁琐流程。
现在的云存储厂商(比如AWS,Google)普遍都支持动态申请空间/释放空间,调用存储厂商一个接口可以完成持久卷的自动创建,K8S支持将云存储厂商封装成为一个StorageClass(存储类),我们在申请存储空间时,只需要创建后端开发工程师去申请PVC,借助StorageClass就可以云存储厂商就能自动帮我们创建好持久卷,K8S则通过StorageClass去封装成为一个PV,并完成PV和PVC的自动绑定。比如我们想要1.5G的空间,那么只需要创建一个PVC,指定StorageClass,云厂商就可以自动为我们分配得到一个1.5GB的持久卷并挂载给Pod进行使用。StorageClass,可以理解成为一个动态的持久卷,支持根据PVC去动态创建PV。
使用StorageClass有以下的好处:
- 1.支持动态分配PV持久卷,提高资源使用率,也提高PV创建的效率。
- 2.支持多种后端存储(AWS、NFS、Ceph等)。
- 3.支持为不同的应用使用不同的存储配置,比如高性能IO系统需要使用SSD,对IO没有要求的系统可以使用HDD。
下面我们演示一下,基于开源项目nfs-subdir-external-provisioner
,支持去将NFS服务去转换成为持久卷,通过一个目录下划分多个子目录去实现逻辑上的持久卷的划分。
2.2 基于NFS封装成为StorageClass
需要注意的是:NFS作为StorageClass时不支持多机部署!!!只能部署单个节点!!!多机部署会出现数据的不一致性问题!!!
(1) 创建namespace存放相关的资源
# nfs-provisioner-namespace.yaml
apiVersion: v1
kind: Namespace
metadata:
name: nfs-provisioner
(2) 创建RBAC权限配置
创建可以访问集群资源的账号并配置相关的权限信息。
# nfs-provisioner-rbac.yaml
apiVersion: v1
kind: ServiceAccount
metadata:
name: nfs-provisioner
namespace: nfs-provisioner
---
kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: nfs-provisioner-runner
rules:
- apiGroups: [""]
resources: ["persistentvolumes"]
verbs: ["get", "list", "watch", "create", "delete"]
- apiGroups: [""]
resources: ["persistentvolumeclaims"]
verbs: ["get", "list", "watch", "update"]
- apiGroups: [""]
resources: ["events"]
verbs: ["create", "update", "patch"]
- apiGroups: ["storage.k8s.io"]
resources: ["storageclasses"]
verbs: ["get", "list", "watch"]
---
kind: Role
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: leader-locking-nfs-provisioner
namespace: nfs-provisioner
rules:
- apiGroups: [""]
resources: ["endpoints"]
verbs: ["get", "watch", "create", "update", "patch"]
---
kind: RoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: leader-locking-nfs-provisioner
namespace: nfs-provisioner
roleRef:
kind: Role
name: leader-locking-nfs-provisioner
apiGroup: rbac.authorization.k8s.io
subjects:
- kind: ServiceAccount
name: nfs-provisioner
namespace: nfs-provisioner
---
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: run-nfs-provisioner
roleRef:
kind: ClusterRole
name: nfs-provisioner-runner
apiGroup: rbac.authorization.k8s.io
subjects:
- kind: ServiceAccount
name: nfs-provisioner
namespace: nfs-provisioner
(3) 创建Deployment部署nfs-provisioner
创建Deployment,并指定已经申请RBAC权限的账号serviceAccountName: nfs-provisioner
。
apiVersion: apps/v1
kind: Deployment
metadata:
name: nfs-subdir-external-provisioner
namespace: nfs-provisioner
labels:
app: nfs-provisioner
spec:
replicas: 1
selector:
matchLabels:
app: nfs-provisioner
template:
metadata:
labels:
app: nfs-provisioner
spec:
serviceAccountName: nfs-provisioner
containers:
- name: nfs-provisioner
image: kubesphere/nfs-subdir-external-provisioner:v4.0.2
env:
- name: PROVISIONER_NAME
value: nfs-provisioner
- name: NFS_SERVER
value: <nfs-server-ip> # 替换为 NFS 服务器的 IP 地址
- name: NFS_PATH
value: /sharedata/nfs # 替换为 NFS 服务器共享的根目录
securityContext:
capabilities:
add:
- DAC_READ_SEARCH
volumeMounts:
- name: nfs-client-root
mountPath: /persistentvolumes
volumes:
- name: nfs-client-root
nfs:
server: <nfs-server-ip> # 替换为 NFS 服务器的 IP 地址
path: /sharedata/nfs # 替换为 NFS 服务器共享的根目录
这里的镜像image
可以使用下面的镜像,pull到本地并上传到远程服务器使用。
docker pull kubesphere/nfs-subdir-external-provisioner:v4.0.2
(4) 创建StorageClass
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: nfs-storage
provisioner: nfs-provisioner
parameters:
pathPattern: ${.PVC.namespace}/${.PVC.name}
archiveOnDelete: "false"
创建名叫nfs-storage
的StorageClass,并指定provisioner
为nfs-provisioner
。通过pathPattern
指定该StorageClass在创建PV时需要怎么去生成路径,我们通过${.PVC.namespace}/${.PVC.name}
指定将创建的PV的路径使用PVC所在的namespace和PVC的name作为子路径,比如wanna-project
下的test-pvc
将会被放在NFS服务器的wanna-project/test-pvc
目录下(算上相对路径的话,就是/sharedata/nfs/wanna-project/test-pvc
)。
需要注意的是,这里的provisioner
需要和Deployment当中指定的PROVISIONER_NAME
保持一致。
- name: PROVISIONER_NAME
value: nfs-provisioner
(5) 执行K8S资源清单创建资源
通过执行下面的命令,去指定资源清单,部署nfs-provisioner
服务,并声明StorageClass。
kubectl apply -f nfs-provisioner-namespace.yaml -f nfs-provisioner-rbac.yaml -f nfs-provisioner-deployment.yaml -f nfs-storageclass.yaml
接着,我们可以通过如下的资源清单nfs-test-pvc.yaml
创建PVC(命令kubectl apply -f nfs-test-pvc.yaml
),通过刚刚我们创建出来的StorageClass去申请PV持久卷。
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: test-pvc
namespace: wanna-project
spec:
accessModes:
- ReadWriteMany
resources:
requests:
storage: 1Gi
storageClassName: nfs-storage
我们使用kubectl get pvc test-pvc -n wanna-project
如下的命令去查看,PVC的状态,可以发现,PVC已经绑定PV成功,绑定的PV名称为pvc-f9a35955-1a44-4c74-b626-f24ec1285b34
,是StorageClass默认生成的名称。
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
test-pvc Bound pvc-f9a35955-1a44-4c74-b626-f24ec1285b34 1Gi RWX nfs-storage 1h