K8S 企业容器云04 - 持久化存储
PV & PVC & StorageClass
持久化
K8S 使用 PersistentVolume(PV) 实现存储持久化
容器 volume,是将一个宿主机的目录和一个容器内目录绑定挂载;
在容器云环境内,对存储资源的持久化需求即 volume 的 “持久性” :
-
容器使用该目录里的内容不受容器生命周期影响,不和当前宿主机绑定;
-
当容器重启或在其他宿主机重建后,仍可通过挂载这个 volume 访问原有存储资源
实现持久化的宿主机目录
分为两个阶段
-
Attach – nodeName
为宿主机节点挂载远程设备(磁盘)
-
Mount – volueme dir
将磁盘设备格式化,并挂载到宿主机特定目录
两阶段实现机制
由独立于 kubelet 主控制循环以外的两个控制循环实现
-
Attach 阶段
AttachDetachController 是 kube-controller-manager 的一部分,运行在 K8S Master 节点
负责调用具体存储资源的 API,无需在宿主机上运行
循环检查 Pod 对应的 PV,和这个 Pod 所在宿主机之间挂载状态,进而决定是否进行 Attach/Detach 操作
-
Mount 阶段
独立于 kubelet 主循环的 VolumeManagerReconciler,避免影响 Pod 创建等主进程
在容器宿主机节点运行
PV & PVC & StorageClass
概念
-
PV - 持久化存储数据卷,是一个 API 对象,用于定义一个持久化存储在宿主机上的目录
-
PVC - 声明容器所需持久化存储资源的属性要求
(即 PVC 提供对某种持久化存储的描述,PV 则负责具体持久化存储的实现)
-
StorageClass - 以 Dynamic Provisioning 机制自动创建所需 PV 的 API 对象
PV & PVC 绑定条件
-
PV 的 spec 字段(资源属性)匹配 PVC 需求;
-
PV 和 PVC 的 storageClassName 一致
PersistentVolumeController 循环遍历查找满足 PVC 条件的 PV,匹配成功则绑定(将 PV 对象的名字填在 PVC 对象的 spec.volumeName 字段)
容器挂载 PV
Pod 的 volumes 字段声明所用 PVC 名称(claimName);
Pod 创建后,kubelet 将 PVC 所匹配到的 PV 挂载到该 Pod
StorageClass
定义两项内容
-
PV 资源属性:存储类型、volume 规格等
-
PV 对应存储插件:
K8S 根据 Pod 提交的 PVC,找到对应 StorageClass,调用 StorageClass 声明的存储插件,创建 PV
运维无需手动创建大量的 PV,只需分类创建几个 StorageClass 供 K8S 自动生成 PV
Local Persistent Volume
使用宿主机本地磁盘目录
-
业务需求:高优先级的系统应用,需要在多个不同节点上存储数据,并且对 I/O 较为敏感
-
典型场景:分布式数据存储如 MongoDB、Cassandra;分布式文件系统如 GlusterFS、Ceph 等
-
安全要求:数据保存在本地硬盘,避免宕机造成数据丢失,需要具备数据备份和恢复能力
设计原则
-
本地磁盘抽象 Local PV
使用独立于宿主机系统以外的磁盘或块设备
-
实现将 Pod 调度到对应 Local PV 所在宿主机
-
常规 PV - 先在宿主机创建 Pod,根据 Pod 需求在该宿主机上创建和持久化 volume
-
Local PV - 控制器在调度 Pod 之前需要明确 volume 分布 (VolumeBindingChecker)
-
测试
使用宿主机挂载内存盘模拟独立磁盘设备
1- 宿主机 k02 创建 RAM Disk
# mkdir /mnt/disks
# for i in vol1 vol2 vol3; do
> mkdir /mnt/disks/$i
> mount -t tmpfs $i /mnt/disks/$i
> done
2- 定义 PV
Local PV 不支持 Dynamic Provisioning ,先手动创建 PV
‘local:’ 指定 Local PersistentVolume 类型
‘path: /mnt/disks/vol1’ 指定该 PV 对应的磁盘路径 (k02 上)
资源目录在 k02 上,通过亲和性 ‘nodeAffinity’ 过滤主机名配对
# vim local-pv.yaml
apiVersion: v1
kind: PersistentVolume
metadata:
name: example-pv
spec:
capacity:
storage: 2Gi
volumeMode: Filesystem
accessModes:
- ReadWriteOnce
persistentVolumeReclaimPolicy: Delete
storageClassName: local-storage
local:
path: /mnt/disks/vol1
nodeAffinity:
required:
nodeSelectorTerms:
- matchExpressions:
- key: kubernetes.io/hostname
operator: In
values:
- k02
# kubectl apply -f local-pv.yaml
persistentvolume/example-pv created
# kubectl get pv
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
example-pv 2Gi RWO Delete Available local-storage 3m50s
3- 定义 StorageClass
创建名为 ‘local-storage’ 的 SC
‘provisioner’ 字段使用 no-provisioner,因为 Local PV 不支持动态创建
‘WaitForFirstConsumer’: 延迟绑定,将 PVC 和 PV 绑定推迟到 ‘调度的时候’,根据 Pod 实际需求情况更新调度规则
# vim local-sc.yaml
kind: StorageClass
apiVersion: storage.k8s.io/v1
metadata:
name: local-storage
provisioner: kubernetes.io/no-provisioner
volumeBindingMode: WaitForFirstConsumer
# kubectl apply -f local-sc.yaml
storageclass.storage.k8s.io/local-storage created
# kubectl get sc
NAME PROVISIONER AGE
local-storage kubernetes.io/no-provisioner 9s
4- 定义 PVC
因为 PVC 的 ‘storageClassName’ 是 local-storage
根据上面 SC 的延迟绑定设置,PVC 暂不配对 PV,进入 ‘Pending’ 状态,等待 Pod 产生时调度
# vim local-pvc.yaml
kind: PersistentVolumeClaim
apiVersion: v1
metadata:
name: example-local-claim
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 2Gi
storageClassName: local-storage
# kubectl apply -f local-pvc.yaml
persistentvolumeclaim/example-local-claim created
# kubectl get pvc
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
example-local-claim Pending local-storage 4s
5- 定义 Pod
‘volumes’ 字段设定 PVC 为 ‘example-local-claim’
Pod 调度后, PVC 状态由 ‘Pending’ 转为 ‘Bound’,延迟绑定生效
# vim local-pod.yaml
kind: Pod
apiVersion: v1
metadata:
name: example-pv-pod
spec:
volumes:
- name: example-pv-storage
persistentVolumeClaim:
claimName: example-local-claim
containers:
- name: example-pv-container
image: nginx
ports:
- containerPort: 80
name: "http-server"
volumeMounts:
- mountPath: "/usr/share/nginx/html"
name: example-pv-storage
# kubectl get pvc
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
example-local-claim Bound example-pv 2Gi RWO local-storage 18m
6- 持久化测试
在容器 volume 内创建文件
# kubectl exec -it example-pv-pod -- /bin/sh
## cd /usr/share/nginx/html
## touch myPVfile.txt
在宿主机 k02 查看内存磁盘空间
# ls /mnt/disks/vol1/
myPVfile.txt
删除、重建测试用 Pod
# kubectl delete -f local-pod.yaml
pod "example-pv-pod" deleted
# kubectl apply -f local-pod.yaml
pod/example-pv-pod created
# kubectl get pods
NAME READY STATUS RESTARTS AGE
example-pv-pod 1/1 Running 0 11s
# kubectl describe pv example-pv
Name: example-pv
Labels: <none>
Annotations: kubectl.kubernetes.io/last-applied-configuration:
{"apiVersion":"v1","kind":"PersistentVolume","metadata":{"annotations":{},"name":"example-pv"},"spec":{"accessModes":["ReadWriteOnce"],"ca...
pv.kubernetes.io/bound-by-controller: yes
Finalizers: [kubernetes.io/pv-protection]
StorageClass: local-storage
Status: Bound
Claim: default/example-local-claim
Reclaim Policy: Delete
Access Modes: RWO
VolumeMode: Filesystem
Capacity: 2Gi
Node Affinity:
Required Terms:
Term 0: kubernetes.io/hostname in [k02]
Message:
Source:
Type: LocalVolume (a persistent volume backed by local storage on a node)
Path: /mnt/disks/vol1
Events: <none>
文件 ‘myPVfile’ 存在,证实持久性
# kubectl exec -it example-pv-pod -- /bin/sh
# ls /usr/share/nginx/html
myPVfile.txt
使用 Static Provisioner 管理 Local PV
https://github.com/kubernetes-sigs/sig-storage-local-static-provisioner