存储卷是定义在Pod之上的可被其内部所有容器挂载是使用的共享目录,该目录实际上是宿主机或外部存储设备之上的存储空间,可以被Pod内的多个容器挂载使用。存储卷独立与Pod的生命周期,因此它存储的数据可以在容器重启或重建后继续使用。
目前k8s支持的存储卷可以大致分为一下几类,它们各自有着不同的实现插件:
通常k8s内置的存储卷插件可以归类为In-Tree类型,它们通k8s源码一起发布迭代,而由存储厂商借助CSI接口扩展的独立于k8s源码的插件统称为Out-of-Tree类型,集群管理员可以根据需要创建自定义的扩展插件,CSI是比较常用的实现方式。目前k8s已经开始逐步将In-Tree类型的存储卷插件迁移到CSI,所以建议在新环境中使用CSI。
emptyDir存储卷可以看作是Pod上的一个临时目录,其生命周期和Pod相同,Pod创建时被创建,Pod删除时被删除,通常用于数据缓存和临时存储。
emptyDir存储卷定义在Pod资源的spec.volumes.emptyDir字段,可嵌套使用的字段有两个:
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/
hostPath是将工作节点上的目录或文件关联到Pod上的一种卷类型,类似于Docker的bind mount,hostPath卷的生命周期和工作节点相同。hostPath存储卷在Pod需要访问节点上的文件时很有用。
hostPath存储卷定义在Pod资源的spec.volumes.hostPathz字段,可嵌套使用的字段有两个:
关于type字段,目前支持的值有下面这些:
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
k8s内置了多种类型的网络存储插件,它们支持的存储服务包括传统的NAS或SAS设备,以及分布式存储、云存储等。网络存储卷的生命周期可以超出工作节点的生命周期,存储卷的回收由集群管理员手动处理,存储的数据可以被Pod重复使用。
下面以NFS存储卷为例进行演示,其它网络存储卷的使用可以参考:https://kubernetes.io/docs/concepts/storage/volumes/
nfs存储卷可以将现有的nfs-server上的存储空间挂载到Pod中使用。当删除Pod时,nfs存储卷的内容会被保留,卷仅是被卸载而不是删除。而且NFS是文件系统级共享服务,支持被多个Pod挂载使用。定义NFS存储卷时支持嵌套使用3个字段:
首先安装一下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是由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处于Pending状态,直到成功关联后转为Available状态。之后如果该PV被PVC绑定后转为Bound状态,直到相应的PVC被删除后会自动解除绑定,PV的状态转换为Released,随后PV的去向将由其回收策略决定。目前PV支持的回收策略有如下3种:
相对应的,PVC在创建之后也处于Pending状态,成功绑定PV后转为Bound状态,被删除后处于Deleted状态
PV资源的spec字段可以嵌套使用下面这些字段:
关于PV的访问模式,目前支持下面4种模式:
关于不同的存储系统支持的访问模式,可以查询: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
PVC隶属于名称空间级别,定义PVC时可以通过访问模式、标签选择器、PV名称和存储资源需求限制多个匹配方式来筛选PV。其中访问模式和资源需求限制是重要的筛选标准。PVC的spec字段支持嵌套使用下面这些字段:
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: nfs-pvc
namespace: default
spec:
accessModes: ["ReadWriteOnce"]
resources:
requests:
storage: 200Mi
limits:
storage: 1Gi
volumeMode: Filesystem
PVC只能被同名称空间下的Pod使用,Pod通过spec.volumes.persistentVolumeClaim引用PVC作为存储卷,可以嵌套使用下面两个字段:
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
尽管PV和PVC简化了用户使用使用存储系统的难度,管理员负责创建PV,用户只需要定义PVC即可。但这种静态PV供给方式也存在一些问题:
更好的解决方案是一种称为动态预配、按需创建PV的机制。集群管理员需要借助StorageClass资源定义一到多个“PV模板”,并在模板中定义好连接到存储系统需要的配置参数。创建PVC时,需要为其指定所属的存储类,而后PV控制器会自动连接到相应存储类上定义的目标存储系统去创建符合PVC需求的PV,并完成PVC和PV的绑定。
存储类(StorageClass)是k8s上API资源类型之一。存储类通常由集群管理员按需创建,用于区分存储类型,比如按存储性能高低、备份策略创建创建不同的存储类。另外,存储类也是PVC筛选PV的条件之一,指定了存储类的PVC只能从其隶属的存储类下筛选PV。但存储类最重要的功能是对PV动态预配的支持。
StorageClass没有spec字段,除了apiVersion、kind和metadata之外,可用的字段如下:
并不是所有的k8s内置存储插件都支持基于存储类实现PV动态预配,关于支持的存储插件可以查询:https://kubernetes.io/zh-cn/docs/concepts/storage/storage-classes/#provisioner
下面以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
创建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
动态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