文章目录
简介
存储卷是定义在Pod之上的可被其内部所有容器挂载是使用的共享目录,该目录实际上是宿主机或外部存储设备之上的存储空间,可以被Pod内的多个容器挂载使用。存储卷独立与Pod的生命周期,因此它存储的数据可以在容器重启或重建后继续使用。
目前k8s支持的存储卷可以大致分为一下几类,它们各自有着不同的实现插件:
- 临时存储卷:emptyDir
- 本地存储卷:hostPath
- 网络存储卷:nfs、cinder、rbd、iscsi等
- 特殊存储卷:configmap、secret
- 扩展支持第三方存储的存储接口:CSI
通常k8s内置的存储卷插件可以归类为In-Tree类型,它们通k8s源码一起发布迭代,而由存储厂商借助CSI接口扩展的独立于k8s源码的插件统称为Out-of-Tree类型,集群管理员可以根据需要创建自定义的扩展插件,CSI是比较常用的实现方式。目前k8s已经开始逐步将In-Tree类型的存储卷插件迁移到CSI,所以建议在新环境中使用CSI。
emptyDir存储卷
emptyDir存储卷可以看作是Pod上的一个临时目录,其生命周期和Pod相同,Pod创建时被创建,Pod删除时被删除,通常用于数据缓存和临时存储。
emptyDir存储卷定义在Pod资源的spec.volumes.emptyDir字段,可嵌套使用的字段有两个:
- medium:用于指定emptyDir存储卷使用的存储介质,可用值为default和Memory,默认是default表示使用节点的默认存储介质(磁盘);Memory表示使用基于内存的临时文件系统tmpfs,可用空间受限于内存,但性能很好,一般用于缓存
- sizeLimit :当前存储卷的空间限额,默认为nil,不限制
apiVersion: apps/v1
kind: Deployment
metadata:
name: volume-emptydir-demo
spec:
replicas: 1
selector:
matchLabels:
app: volume-emptydir-demo
template:
metadata:
labels:
app: volume-emptydir-demo
spec:
containers:
- name: nginx
image: nginx
imagePullPolicy: IfNotPresent
ports:
- name: http
containerPort: 80
volumeMounts:
- name: cache-volume
mountPath: /cache
volumes:
- name: cache-volume
emptyDir:
medium: Memory
sizeLimit: 128Mi
进入Pod验证
emptyDir其实就是node节点上的一个目录,它存放在 /var/lib/kubelet/pods/<pod-id>/volumes/kubernetes.io~empty-dir/ 下
hostPath存储卷
hostPath是将工作节点上的目录或文件关联到Pod上的一种卷类型,类似于Docker的bind mount,hostPath卷的生命周期和工作节点相同。hostPath存储卷在Pod需要访问节点上的文件时很有用。
hostPath存储卷定义在Pod资源的spec.volumes.hostPathz字段,可嵌套使用的字段有两个:
- path <string>:用于指定工作节点上的目录路径
- type <string>:用于指定工作节点之上的存储类型
关于type字段,目前支持的值有下面这些:
- DirectoryOrCreate:指定的目录不存在时,自动将其创建为0755权限的空目录,属主和属组都为kubelet
- Directory:事先必须存在的目录
- FileOrCreate:指定的不存在时,自动将其创建为0644权限的空文件,属主和属组都为kubelet
- File:事先必须存在的文件
- Socket:事先必须存在的Socket文件
- CharDevice:事先必须存在的字符设备路径
- BlockDevice:事先必须存在的文件设备路径
- “”:空字符串,默认值,用于向后兼容,在关联hostPath卷之前不进行任何检查
apiVersion: apps/v1
kind: Deployment
metadata:
name: volume-hostpath-demo
spec:
replicas: 1
selector:
matchLabels:
app: volume-hostpath-demo
template:
metadata:
labels:
app: volume-hostpath-demo
spec:
containers:
- name: nginx
image: nginx
imagePullPolicy: IfNotPresent
ports:
- name: http
containerPort: 80
volumeMounts:
- name: cache-volume
mountPath: /cache
volumes:
- name: cache-volume
hostPath:
path: /data/kubernetes
进入Pod创建一个文件
在主机上验证文件是否存在
网络存储卷
k8s内置了多种类型的网络存储插件,它们支持的存储服务包括传统的NAS或SAS设备,以及分布式存储、云存储等。网络存储卷的生命周期可以超出工作节点的生命周期,存储卷的回收由集群管理员手动处理,存储的数据可以被Pod重复使用。
下面以NFS存储卷为例进行演示,其它网络存储卷的使用可以参考:https://kubernetes.io/docs/concepts/storage/volumes/
NFS存储卷
nfs存储卷可以将现有的nfs-server上的存储空间挂载到Pod中使用。当删除Pod时,nfs存储卷的内容会被保留,卷仅是被卸载而不是删除。而且NFS是文件系统级共享服务,支持被多个Pod挂载使用。定义NFS存储卷时支持嵌套使用3个字段:
- server:NFS Server的地址或域名
- path:NFS Server共享的目录
- readOnly:是否以只读方式挂载,默认false
首先安装一下nfs-server,地址是192.168.122.1
apt -y install nfs-server
mkdir -p /data/k8s
vim /etc/exports
/data/k8s *(rw,no_root_squash,subtree_check)
exportfs -r
showmount -e 192.168.122.1
所有k8s工作节点都要安装nfs客户端
apt -y install nfs-common
nfs存储卷示例
apiVersion: apps/v1
kind: Deployment
metadata:
name: volume-nfs-demo
spec:
replicas: 1
selector:
matchLabels:
app: volume-nfs-demo
template:
metadata:
labels:
app: volume-nfs-demo
spec:
containers:
- name: nginx
image: nginx
volumeMounts:
- name: nfs-volume
mountPath: /usr/share/nginx/html/mysite
volumes:
- name: nfs-volume
nfs:
server: 192.168.122.1
path: /data/k8s
等Pod创建之后,在nfs共享目录写入一个文件,然后访问nginx验证
PV和PVC
PV和PVC介绍
PV是由k8s集群管理员在全局级别配置的存储卷,它通过支持的存储卷插件及给定的配置参数关联到指定存储系统的存储空间,这个的存储空间可能是ceph rbd-image、nfs共享的目录和cephfs文件系统等,也就是说PV的数据最终是保存在后端的存储系统上的。PV将存储系统上的存储空间抽象为集群级别的API资源,由管理员负责创建维护。
将PV提供的存储空间用于Pod对象存储卷时,用户需要先在namespace中创建PVC资源声明需要的存储空间大小和访问模式等属性,接下来PV控制器会选择合适的PV与PVC进行绑定。随后,在Pod资源中通过persistenVolumeCliam卷插件指定要使用的PVC对象就可以使用这个PVC绑定的PV的存储空间。
总结来说,PV和PVC就是在用户和存储系统之间添加的一个中间层,管理员事先定义好PV,用户通过PVC声明要使用的存储特性来绑定符合条件的最佳PV,从而实现了用户和存储系统之间的解耦,用户不需要了解存储系统的具体使用方式,只需要定义PVC就可以。
PV生命周期
PV生命周期中存在几个状态,创建后未正确关联到存储设备的PV处于Pending状态,直到成功关联后转为Available状态。之后如果该PV被PVC绑定后转为Bound状态,直到相应的PVC被删除后会自动解除绑定,PV的状态转换为Released,随后PV的去向将由其回收策略决定。目前PV支持的回收策略有如下3种:
- Delete:删除PVC后,删除其所绑定的PV和PV关联的存储设备
- Retain:默认策略,删除PVC后将保留其所绑定的PV和存储的数据,但会将PV至于Released状态,此时PV不能被其它PVC绑定使用,需要管理员执行后续回收操作
- Recycle(已废弃):删除PVC时其所绑定的PV上存储的数据会被清空,而后PV转为Available状态,可以再次被其它PVC绑定使用,此策略已废弃
相对应的,PVC在创建之后也处于Pending状态,成功绑定PV后转为Bound状态,被删除后处于Deleted状态
静态PV
PV资源的spec字段可以嵌套使用下面这些字段:
- <volume-plugin> <Object>:具体存储卷插件配置,用来指定PV关联的存储设备,和Pod直接通过存储卷插件定义卷的参数一致
- accessModes <[]string>:指定PV的访问模式,目前支持
- capacity <map[string]string>:指定PV的容量
- mountOptions <[]string>:挂载选项
- nodeAffinity <Object>:节点亲和性,用于限制能访问该PV的节点
- persistentVolumeReclaimPolicy <string>:当前PV的回收策略
- volumeMode <string>:该PV的卷模型,用于指定此存储卷是被格式化为文件系统使用还是直接作为裸格式块设备使用,默认为Filesystem
- storageClassName <string>:此PV所属的存储类名称,默认为空,不属于任何存储类
关于PV的访问模式,目前支持下面4种模式:
- ReadOnlyMany:简写为ROX,卷可以被多个节点以只读方式挂载
- ReadWriteOnce:简写为RWO,卷只能被一个节点以读写方式挂载,但ReadWriteOnce 访问模式也允许运行在同一节点上的多个 Pod 访问卷
- ReadWriteMany:简写为RWX,卷可以被多个节点以读写方式挂载
- ReadWriteOncePod:简写为RWOP,卷只能以读写方式被单个Pod挂载使用
关于不同的存储系统支持的访问模式,可以查询:https://kubernetes.io/zh-cn/docs/concepts/storage/persistent-volumes/#access-modes
下面以NFS PV为例演示:
apiVersion: v1
kind: PersistentVolume
metadata:
name: nfs-pv-demo
spec:
nfs:
server: 192.168.122.1
path: /data/k8s
accessModes: ["ReadWriteMany"]
capacity:
storage: 1Gi
persistentVolumeReclaimPolicy: Retain
volumeMode: Filesystem
查看PV状态
PVC
PVC隶属于名称空间级别,定义PVC时可以通过访问模式、标签选择器、PV名称和存储资源需求限制多个匹配方式来筛选PV。其中访问模式和资源需求限制是重要的筛选标准。PVC的spec字段支持嵌套使用下面这些字段:
- accessModes <[]string>:PVC的访问模式,可用值和PV的访问模式一致
- resources <Object>:声明PVC要使用存储空间的需求和限制
- dataSource <Object>:用于从指定的数据源恢复该PVC卷,目前支持从卷快照或已存在的PVC恢复
- selector <Object>:标签选择器,用于筛选PV
- storageClassName <string>:该PVC隶属的存储类
- volumeMode <string>:卷模型,用于指定此存储卷是被格式化为文件系统使用还是直接使用裸格式的块设备;默认为Filesystem
- volumeName <string>:直接指定要绑定的PV资源名称
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: nfs-pvc
namespace: default
spec:
accessModes: ["ReadWriteOnce"]
resources:
requests:
storage: 200Mi
limits:
storage: 1Gi
volumeMode: Filesystem
查看PVC状态验证
PVC使用
PVC只能被同名称空间下的Pod使用,Pod通过spec.volumes.persistentVolumeClaim引用PVC作为存储卷,可以嵌套使用下面两个字段:
- claimName:指定要引用的PVC的名称
- readOnly:是否以只读模式挂载,默认False
apiVersion: apps/v1
kind: Deployment
metadata:
name: nfs-pvc-demo
spec:
replicas: 3
selector:
matchLabels:
app: nfs-pvc-demo
template:
metadata:
labels:
app: nfs-pvc-demo
spec:
containers:
- name: nginx
image: nginx
imagePullPolicy: IfNotPresent
volumeMounts:
- name: nfs-pvc-volume
mountPath: /usr/share/nginx/html/static
volumes:
- name: nfs-pvc-volume
persistentVolumeClaim:
claimName: nfs-pvc
访问Pod测试
动态PV
尽管PV和PVC简化了用户使用使用存储系统的难度,管理员负责创建PV,用户只需要定义PVC即可。但这种静态PV供给方式也存在一些问题:
- 管理员无法预测用户的存储需求,很容易会导致PVC无法匹配到合适的PV被挂起,直到管理员介入解决
- 那些能匹配到PV的PVC可能存在存储空间浪费现象,比如一个需求5G的PVC绑定到一个个10G的PV
更好的解决方案是一种称为动态预配、按需创建PV的机制。集群管理员需要借助StorageClass资源定义一到多个“PV模板”,并在模板中定义好连接到存储系统需要的配置参数。创建PVC时,需要为其指定所属的存储类,而后PV控制器会自动连接到相应存储类上定义的目标存储系统去创建符合PVC需求的PV,并完成PVC和PV的绑定。
StorageClass
存储类(StorageClass)是k8s上API资源类型之一。存储类通常由集群管理员按需创建,用于区分存储类型,比如按存储性能高低、备份策略创建创建不同的存储类。另外,存储类也是PVC筛选PV的条件之一,指定了存储类的PVC只能从其隶属的存储类下筛选PV。但存储类最重要的功能是对PV动态预配的支持。
StorageClass没有spec字段,除了apiVersion、kind和metadata之外,可用的字段如下:
- allowVolumeExpansion <bool>:是否支持存储卷空间扩展功能
- provisioner <string>:必选字段用于指定存储服务方,存储类要基于该字段确定要使用的存储插件,以便连接到目标存储系统
- parameters <map[string]string>:连接到目标存储系统时需要用到的参数
- reclaimPolicy <string>:当前存储类动态创建的PV的回收策略,默认为Delete
- mountOptions <[]string>:当前存储类动态创建的PV的默认挂载选项
并不是所有的k8s内置存储插件都支持基于存储类实现PV动态预配,关于支持的存储插件可以查询:https://kubernetes.io/zh-cn/docs/concepts/storage/storage-classes/#provisioner
NFS StorageClass
下面以nfs为例,由于内置的nfs存储插件不支持PV动态预配,所以要借助第三方项目实现:https://github.com/kubernetes-sigs/nfs-subdir-external-provisioner
下载相关的部署文件
mkdir ./nfs-provisioner && cd ./nfs-provisioner
wget https://github.com/kubernetes-sigs/nfs-subdir-external-provisioner/archive/refs/tags/v4.0.2.zip
unzip v4.0.2.zip
cd nfs-subdir-external-provisioner-4.0.2/deploy/
修改部署文件,与环境匹配
kubectl create ns storage
NAMESPACE=storage
sed -i'' "s/namespace:.*/namespace: $NAMESPACE/g" ./rbac.yaml ./deployment.yaml #修改namespace值,将资源部署到storage namespace
vim deployment.yaml
###################
spec:
serviceAccountName: nfs-client-provisioner
containers:
- name: nfs-client-provisioner
image: easzlab/nfs-subdir-external-provisioner:v4.0.1 #修改镜像为国内的镜像源
volumeMounts:
- name: nfs-client-root
mountPath: /persistentvolumes
env:
- name: PROVISIONER_NAME
value: k8s-sigs.io/nfs-subdir-external-provisioner
- name: NFS_SERVER
value: 192.168.122.1 #修改为nfs-server的地址
- name: NFS_PATH
value: /data/k8s #修改为nfs-server的共享目录路径
volumes:
- name: nfs-client-root
nfs:
server: 192.168.122.1 #修改为nfs-server的地址
path: /data/k8s #修改为nfs-server的共享目录路径
部署nfs-provisioner
kubectl apply -f rbac.yaml
kubectl apply -f deployment.yaml
查看Pod状态,验证nfs-provisoner已成功运行
创建StorageClass
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: managed-nfs-storage
provisioner: k8s-sigs.io/nfs-subdir-external-provisioner
parameters:
archiveOnDelete: "false"
reclaimPolicy: Retain
查看StorageClass资源
PV动态预配
动态PV预配功能的使用有两个前置条件:支持动态PV创建功能的卷插件,以及一个使用了对应于该存储卷插件的后端存储系统的StorageClass资源。
还是以nfs为例,先定义PVC:
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: nfs-pvc-dynamic
namespace: default
spec:
accessModes: ["ReadWriteOnce"]
resources:
requests:
storage: 500Mi
limits:
storage: 1Gi
volumeMode: Filesystem
storageClassName: managed-nfs-storage #指定StorageClass为上一步创建的StorageClass
PVC创建后,PV会被自动创建并绑定到PVC
在nfs-server上也会为PV自动创建一个目录
配置Pod使用PVC测试
apiVersion: apps/v1
kind: Deployment
metadata:
name: nfs-pvc-dynamic-demo
spec:
replicas: 3
selector:
matchLabels:
app: nfs-pvc-dynamic-demo
template:
metadata:
labels:
app: nfs-pvc-dynamic-demo
spec:
containers:
- name: nginx
image: nginx
imagePullPolicy: IfNotPresent
volumeMounts:
- name: nfs-pvc-dynamic-volume
mountPath: /usr/share/nginx/html/static
volumes:
- name: nfs-pvc-dynamic-volume
persistentVolumeClaim:
claimName: nfs-pvc-dynamic
进入Pod写入测试数据,然后访问测试