# 28 pv,pvc,storageClass这到底是说的啥
# PV
PV:持久化存储数据卷,定义的是一个持久化存储在宿主机上的目录,比如一个NFS的挂载目录
apiVersion: v1 kind: PersistentVolume metadata: name: nfs spec: storageClassName: manual capacity: storage: 1Gi accessModes: - ReadWriteMany nfs: server: 10.2.1.4 path: "/"
1
2
3
4
5
6
7
8
9
10
11
12
13PVC:描述的是Pod希望使用的持久化存储的属性
apiVersion: v1 kind: PersistentVolumeClaim metadata: name: nfs spec: accessModes: - ReadWriteMany storageClassName: manual resources: requests: storage: 1Gi
1
2
3
4
5
6
7
8
9
10
11绑定:
- PV和PVC的spec字段,比如存储大小必须满足PVC的要求
- storageClassName必须一样
声明使用
apiVersion: v1 kind: Pod metadata: labels: role: web-frontend spec: containers: - name: web image: nginx ports: - name: web containerPort: 80 volumeMounts: - name: nfs mountPath: "/usr/share/nginx/html" volumes: - name: nfs persistentVolumeClaim: claimName: nfs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19大多数的情况下,持久化的Volume的实现,依赖一个远程存储服务,如NFS、GlusterFS,远程块存储,比如公有云提供的远程磁盘等
持久化是指容器在这个目录里写入的文件,都会保存在远程存储中,从而使这个目录有了持久性
默认情况下,目录是宿主机的一个目录:
/var/lib/kubelet/pods/<Pod ID>/volumes/kubernetes.io~<Volume类型>/<Volume名字>
- 第一阶段:attach 如果是远程块存储,如GCE提供的远程磁盘服务,先调用GOOGLE的API,将它提供的PersistentDisk挂载到Pod所在的宿主机上,相当于执行
gcloud compute instances attach-disk <虚拟机名字> --disk <远程磁盘名字>
- 第二阶段:mount 将磁盘设备格式化并挂载到Volume宿主机目录的操作,一般称为Mount
- 第一阶段提供可用的参数是nodeName,宿主机的名字,第二阶段提供的是dir,即Volume的宿主机目录
- 容器中挂载这个Volume,`docker run -v /var/lib/kubelet/pods/
/volumes/kubernetes.io~<Volume类型>/<Volume名字>:/<容器的目标目录> 我的镜像 ...
- 第一阶段:attach 如果是远程块存储,如GCE提供的远程磁盘服务,先调用GOOGLE的API,将它提供的PersistentDisk挂载到Pod所在的宿主机上,相当于执行
反向操作
- Unmount
- Dettach
# 维护
- 第一阶段:Volume Controller负责,名字AttachDetachController,不断检查每个Pod对应的PV,和这个Pod所在宿主机之间的挂载情况,从而决定,是否需要对这个PV进行Attach或Dettach操作 内置的控制器,kube-controller-manager的一部分,运行在master节点上
- 第二阶段:必须发生在Pod对应的宿主机上,它是kubelet组件的一部分,名字VolumeManagerReconciler,是一个独立于kubelet主循环的Goroutine
kubelet主要设计的一个原则:主控制循环绝对不可以被block
# StorageClass
k8s为我们提供了一套自动创建PV的机制,Dynamic Provisioning,前面人工管理的方式叫Static Provisioning
StorageClass是创建PV的模板
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: block-service
provisioner: kubernets.io/gce-pd # 供应者
parameters:
type: pd-ssd
2
3
4
5
6
7
# 29 PV PVC是否多此一举
Local Persistent Volume的设计难点
- 如何抽象成PV 绝不能把一个宿主机的目录当作PV使用,本地目录的存储行为完全不可控,磁盘可能被其它应用写满,造成整个宿主机宕机。而且不同的本地目录也没有办法做IO隔离机制 所以一个Local Persistent Volume的存储介质一定是一块额外挂载在宿主机上的磁盘或设备。不应该是和宿主机根目录使用的主硬盘。一个PV一块盘
- 调度器如何保证Pod始终能被正确调度到所请求的Local Persistent Volume所在的节点上呢 常规PV都是先调度Pod到节点上,然后再通过两阶段处理来执久化这台机器上的Volume目录,进而完成Volume目录与容器的绑定挂载 调度时考虑Volume分布:调度器必须知道所有节点和Local Persistent Volume对应关联关系,然后根据这个来调度Pod。调度器中VolumeBindingChecker过滤条件专门负责
所以在开始使用“Local Persistent Volume“之前,首先在集群中需要配置好磁盘或志设备
apiVersion: v1
kind: PersistentVolume
metadata:
name: example-pv
spec:
capacity:
storage: 5Gi
volumeMode: Filesystem
accessModes:
- ReadWriteOnce
persistentVolumeReclaimPolicy: Delete
storageClassName: local-storage
local:
path: /mnt/disks/vol1
nodeAffinity:
required:
nodeSelecotrTerms:
- matchExpressions:
- key: kubernetes.io/hostname
operator: In
values:
- node-1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
kubectl create -f local-pv.yaml
kubectl get pv
2
最佳实践,要创建一个StorageClass来描述这个PV,延迟绑定,推迟到调度的时候
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: local-storage
provisioner: kubernates.io/no-provisioner # 供应者
# 告诉VolumeController,不要现在进行绑定(设置PVC的VolumeName字段)将原本实时发生的PVC和PV的绑定过程,延迟到Pod第一次调度时在调度器中进行,从而保证了这个绑定结果不会影响Pod的正常 调度
volumeBindingMode: WaitForFirstConsumer
2
3
4
5
6
7
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: example-local-claim
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 5Gi
storageClassName: local-storage # Volume Cotrller看到它时,不会为它进行绑定操作
2
3
4
5
6
7
8
9
10
11
# 限制
- 创建PV的操作不可省略,不支持Dynamic Provisioning,没有办法在创建pvc时创建pv
- WaitForFirstConsumer属性,延迟绑定,推迟到调度的时候
具体实现时,调度器实际维护了一个与Volume Controller类似的控制循环,专门负责为“延迟绑定”的PV和PVC进行绑定操作
$ kubectl create -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 7s
2
3
4
5
6
创建后仍处理Pending状态,也就是等待绑定的状态
apiVersion: v1
kind: Pod
metadata:
name: example-pv-pod
spec:
containers:
- name: example-pv-container
image: nginx
ports:
- containerPort: 80
name: http-server
volumeMounts:
- mountPath: /usr/share/nginx/html
name: example-pv-storage
volumes:
- name: example-pv-storage
persistentVolumeClaim:
claimName: example-local-claim # 定义了PVC
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 清理策略
- 删除使用这个PV的Pod
- 从宿主机移除本地磁盘
- 删除pvc
- 删除pv
# 简化策略
Static Provisioner:启动后,它会通过DaemonSet,自动检查每个宿主机的/mnt/disks目录,然后调用api为这些目录的每一个挂载,创建一个对应的pv出来
# 编写自己的存储插件
# 方式1:Flexvolume
apiVersion: v1
kind: PersistentVolume
metadata:
name: pv-flex-nfs
spec:
capacity:
storage: 10Gi
accessModes:
- ReadWriteMany
flexVolume:
driver: k8s/nfs # 插件名,提供商+存储驱动,会使用k8s~nfs做为插件名
fsType: nfs
options:
server: 10.10.0.25
share: export
2
3
4
5
6
7
8
9
10
11
12
13
14
15
命令/usr/libexec/kubernetes/kubelet-plugins/volume/exec/k8s~nfs/nfs mount <mount dir> <json param>
操作的参数:init、mount、unmount、attach以用户dettach等
缺点
- 一不支持Dynamic Provisioning(即:为每个PVC自动创建PV及对应的Volume),除非专门写一个External Provisioner
- 挂载信息无法在解挂载时直接使用
# 方式2 CSI(Container Storage Interface)
**设计思想:把Provision阶段,及k8s里的一部分存储管理的功能,从主干代码中剥离,做成了几个单独的组件。**组件通过Watch API监听k8s中与存储有关的事件变化,比如pvc创建,来执行具体的存储管理动作。
把插件职责从'两阶段处理‘, 扩展成Provision 创建磁盘 Attach 挂载到虚拟机 Mount 格式化后挂载到宿主机上 三个阶段

持久卷类型
- PV
- CSI Volume
三个独立的外部组件(External Components)对应从k8s中剥离出来的存储管理功能,Mount阶段不是它的职责,而是在Kubelet的VolumeManagerReconciler控制循环调用
**Driver Registrar:**需要请求CSI Identity来获取插件信息
**External Provisioner:**负责Provision阶段。
监听API Server里的PVC对象。当一个PVC创建时,它会调用CSI Controller的CreateVolume方法创建PV
**External Attacher:**负责Attach阶段。
监听API Server里VolumeAttachment对象的变化。此对象是k8s中确认一个Volume可进行Attach附件的重要标志 一旦发现VolumeAttachment对象,它就会调用CSI Controller的ControllerPublish方法,完成对应Volume的Attach阶段
CSI三个服务
- CSI Identity:注册插件到 Driver Registrar,负责对外暴露插件本身的信息
- CSI Controller:定义对CSI Volume(对应PV)的管理接口。它们无需要在宿主机上进行,而是属于Volume Controller的逻辑,属于Master节点的一部分
- CSI Node:在宿主机上执行的操作
流程:
- AttachDetachContrller需要执行Attach操作时,会执行pkg/volume/csi目录中,创建一个VolumeAttachment对象,进而触发External Attacher调用CSI Contrller服务的ControllerPublisherVolume方法
- VolumeManagerReconciler需要Mount操作时,它实际上也会到png/volume/csi目录中,直接向CSI Node服务发起NodePublishVolume方法的请求
# 31 CSI插件编写指南
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: db-block-storage
namespace: kubee-system
annotations:
# 作为默认的持久化存储提供者
storageclass.kubernetes.io/is-default-class: true
# 使用com.digitalocean.csi.dobs的csi插件处理这个SC在的所有操作
provisioner: com.digitalocean.csi.dobs
2
3
4
5
6
7
8
9
10
代码结构
tree $GOPATH/src/github.com/digitalocean/csi-digitalocean/driver
$GOPATH/src/github.com/digitalocean/csi-digitalocean/driver
├── controller.go
├── driver.go
├── identity.go
├── mounter.go
└── node.go
2
3
4
5
6
7
接口定义地址:https://github.com/container-storage-interface/spec/blob/master/csi.proto
在定义 DaemonSet Pod 的时候,我们需要把宿主机的 /var/lib/kubelet 以 Volume 的方式挂载进 CSI 插件容器的同名目录下,然后设置这个 Volume 的 mountPropagation=Bidirectional,即开启双向挂载传播,从而将容器在这个目录下进行的挂载操作“传播”给宿主机,反之亦然。
← 16 控制器模式(设计方法) 三层网络实现 →