1. 什么是存储卷(Volume)

容器和Pod生命周期可能很短,会被频繁地销毁和创建。容器销毁时,保存在容器内部文件系统中的数据都会被清除,为了持久化保存容器的数据,可以使用Kubernetes 中的卷(Volume);

通俗点讲,Volume就是一个目录,这一点与Docker Volume类似。当VolumemountPodPod中的所有容器都可以访问这个Volume

Volume的生命周期独立于容器,Pod中的容器可能被销毁和重建,但Volume会被保留。

2. 存储卷分类

Kubernetes支持非常丰富的存储卷类型,包括本地存储(节点)和网络存储,下面只列出一些常见的类型:

  • emptyDir: 用于存储临时数据的简单空目录。
  • hostPath: 用于将目录从工作节点的文件系统挂载到pod中。
  • persistentVolumeClaim: 用来将持久卷PersistentVolume)挂载到 Pod 中。
  • ….

查看更多支持类型:https://kubernetes.io/zh-cn/docs/concepts/storage/volumes/

3. emptyDir 使用

emptyDir是最基础的Volume类型,emptyDir Volume 对于容器来说是持久的,对于Pod则不是。当Pod从节点删除时,Volume的内容也会被删除。但如果只是容器被销毁而Pod还在,则Volume不受影响。也就是说:emptyDir Volume的生命周期与Pod一致。

Pod中的所有容器都可以共享Volume它们可以指定各自的mount路径。下面通过例子来实践emptyDir,配置文件如下:

3.1 配置文件

文件:empty-volume-deploy.yaml

apiVersion: v1
kind: Pod
metadata:
name: emptydir-demo
spec:
containers:
- image: busybox
name: write-box
volumeMounts:
- mountPath: /write-dir # 写容器将emptydir-volume挂载到 /write-dir
name: emptydir-volume
args:
- /bin/sh
- -c
- echo "hello world" > /write-dir/hello; sleep 30000 # 写数据到文件
- image: busybox
name: read-box
volumeMounts:
- mountPath: /read-dir # 读容器将emptydir-volume挂载到 /read-dir
name: emptydir-volume
args:
- /bin/sh
- -c
- cat /read-dir/hello; sleep 30000 # 从文件中读出

volumes: # 定义一个名字为:emptydir-volume的emptyDir类型卷
- name: emptydir-volume
emptyDir: {}

3.2 发布验证

# 发布资源
$ kubectl apply -f empty-volume-deploy.yaml
pod/emptydir-demo created
# 查看读容器是否度到数据
$ kubectl logs emptydir-demo read-box
hello world

4. hostPath 使用

hostPath Volume的作用是将节点(宿主机)文件系统中已存在的目录挂载给Pod的容器。大部分应用都不会使用hostPath Volume,因为这实际上增加了Pod与节点的耦合,限制了Pod的使用。不过那些需要访问KubernetesDocker内部数据(配置文件和二进制库)的应用则需要使用hostPath

如果Pod被销毁了,hostPath对应的目录还是会被保留,从这一点来看,hostPath的持久性比emptyDir强。不过一旦Host崩溃,hostPath也就无法访问了。

4.1 配置文件

文件: hostpaht-volume-deploy.yaml

apiVersion: v1
kind: Pod
metadata:
name: hostpath-demo
spec:
containers:
- image: busybox
name: go-box
volumeMounts:
- mountPath: /home/go
name: hostpath-volume
args:
- /bin/sh
- -c
- echo "hello world" > /home/go/hello; sleep 30000 # 写数据到文件

volumes: # 定义hostPath类型卷
- name: hostpath-volume
hostPath:
path: /home/go

4.2 发布验证

# 发布资源
$ kubectl apply -f hostpaht-volume-deploy.yaml
pod/hostpath-demo created
# 查看pod 所在节点IP
$ kubectl describe pod hostpath-demo
Name: hostpath-demo
Namespace: default
Priority: 0
Node: node2/192.168.148.132 # 这里看到节点IP
Start Time: Fri, 02 Sep 2022 19:02:35 +0800
...
# 到对应的节点查看文件信息
[root@node2 go]$ cat /home/go/hello
hello world

5. PV和PVC

5.1 概念

Volume虽然提供了非常好的数据持久化方案,但是管理性上还有不足。

Pod通常是由应用的开发人员维护,而Volume则通常是由存储系统的管理员维护。开发人员要获得上面的信息,要么询问管理员,要么自己就是管理员。

这样就带来一个管理上的问题: 应用开发人员和系统管理员的职责耦合在一起了。如果系统规模较小或者对于开发环境,这样的情况还可以接受,当集群规模变大,特别是对于生成环境,考虑到效率和安全性,这就成了必须要解决的问题。

为了解决上面的问题,Kubernetes给出的解决方案是PersistentVolume(PV)PersistentVolumeClaim(PVC)

PersistentVolume(PV)PersistentVolumeClaim(PVC)说明:

  • PersistentVolume(PV):是外部存储系统中的一块存储空间,由管理员创建和维护。与Volume一样,PV具有持久性,生命周期独立于Pod
  • PersistentVolumeClaim (PVC): 是对PV的申请(Claim)。PVC通常由普通用户创建和维护。需要为Pod分配存储资源时,用户可以创建一个PVC,指明存储资源的容量大小和访问模式(比如只读)等信息,Kubernetes会查找并提供满足条件的PV

5.2 关系图

持久卷的创建&使用流程

6.PV & PVC初使用

6.1 创建PV

下面示例是基于NFS文件共享系统为存储空间,至于NFS文件系统安装,此处省略。

6.1.1 配置文件

apiVersion: v1
kind: PersistentVolume
metadata:
name: nfs-pv
spec:
capacity:
storage: 1Gi # 指定PV的容量为1G
accessModes:
- ReadWriteOnce # 访问模式
persistentVolumeReclaimPolicy: Retain # 指定PV的回收策略
storageClassName: nfs # 指定PV的class为nfs
nfs:
path: /data/pv1 # 指定PV在NFS服务器上的对应的目录
server: 192.168.148.130 # NFS服务器IP

配置参数说明:

accessModes:访问模式

  • ReadWriteOnce(RWO): 仅允许单个节点挂载读写;
  • ReadOnlyMany(ROX): 允许多个节点挂载只读 ;
  • ReadWriteMan(RWX): 允许多个节点挂载读写 ;

persistentVolumeReclaimPolicy:回收策略

  • Retain : 需要手工回收;

  • Recycle: 自动回收,即删除存储卷目录下的所有文件(包括子目录和隐藏文件),效果相当于执行rm -rf/xx/*,目前仅NFShostPath支持此操作;

  • Delete: 删除存储卷,仅部分云端存储系统支持,如AWS EBS、GCE PD、Azure Disk和Cinder

6.1.2 创建&发布

# 创建资源
$ kubectl apply -f nfs-pv.yaml
persistentvolume/nfs-pv created
# 查询状态
$ kubectl get pv
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS STORAGECLASS ..
nfs-pv 1Gi RWO Retain Available nfs ..

PV状态说明

  • Available: 可用状态的自由资源,尚未被PVC绑定。
  • Bound:已经绑定至某PVC
  • Released:绑定的PVC已经被删除,但资源尚未被集群回收。
  • Failed:因自动回收资源失败而处于的故障状态。

6.2 创建PVC

6.2.1 配置文件

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: nfs-pvc1
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 1Gi
storageClassName: nfs

6.2.2 创建&绑定

# 创建PVC
$ kubectl apply -f nfs-pvc1.yaml
persistentvolumeclaim/nfs-pvc1 created
# 查看PVC状态
$ kubectl get pvc
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
nfs-pvc1 Bound nfs-pv 1Gi RWO nfs 4s
# 查看PV状态
$ kubectl get pv
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS ...
nfs-pv 1Gi RWO Retain Bound default/nfs-pvc1 nfs ...

通过PVPVC的状态都是Bound,则说明已经绑定成功。

6.3 使用PVC

经过上面几个步骤,已经成功创建了持久卷,接下来就可以在Pod中使用存储了。

6.3.1 配置文件

apiVersion: v1
kind: Pod
metadata:
name: nfs-demo
spec:
containers:
- name: pvc-pod
image: busybox
volumeMounts:
- mountPath: /pvc
name: pvc-volume
args:
- /bin/sh
- -c
- echo "hello world" > /pvc/hello-pvc; sleep 30000 # 写数据到文件
volumes: # 使用以下方式关联pvc
- name: pvc-volume
persistentVolumeClaim:
claimName: nfs-pvc1 # 和之前创建的pvc名称一致

6.3.2 发布 & 验证

# 发布资源
$ kubectl apply -f pvc-pod.yaml
# 查看nfs服务文件
$ tree -l data
data
└── pv1
└── hello-pvc
# 进入pod,查看pod内部文件
$ kubectl exec -it nfs-demo /bin/sh
/ # ls
bin dev etc home proc pvc root sys tmp usr var
/ # cd pvc/
/pvc # ls
hello-pvc

7. PV动态供给(StorageClass)

在前面的例子中,我们提前创建了PV,然后通过PVC申请PV并在Pod中使用,这种方式叫作静态供给(Static Provision)。

与之对应的是动态供给(Dynamical Provision),即如果没有满足PVC条件的PV,会动态创建PV。相比静态供给,动态供给有明显的优势:不需要提前创建PV,减少了管理员的工作量,效率高。

动态供给是通过StorageClass实现的,每个StorageClass 都包含 provisionerparametersreclaimPolicy 字段, 这些字段会在 StorageClass 需要动态分配 PersistentVolume 时会使用到。

StorageClass 支持多种类型的卷插件(Provisioner),不同的Provisoner的创建方法各有不同,参数也会有所区别,下面以NFS卷插件为例,进行学习。其他插件可参见文档: https://kubernetes.io/zh-cn/docs/concepts/storage/storage-classes/#aws-ebs

7.1 NFS驱动安装

因为Kubernetes 不包含内部 NFS 驱动。所以需要使用外部驱动为 NFS 创建 StorageClass,创建文档参考https://github.com/kubernetes-sigs/nfs-subdir-external-provisioner

7.1.1 使用helm安装

# 添加仓库
$ helm repo add nfs-subdir-external-provisioner https://kubernetes-sigs.github.io/nfs-subdir-external-provisioner/

# 安装
$ helm install nfs-client nfs-subdir-external-provisioner/nfs-subdir-external-provisioner \
--set nfs.server=192.168.148.130 \ # nfs服务器IP
--set nfs.path=/data/share \ # nfs共享目录
--set image.repository=eipwork/nfs-subdir-external-provisioner \ # 更换镜像地址
--set storageClass.name=nfs-client \
--set storageClass.defaultClass=true # 开启默认使用的nfs的storageclass

7.1.2 查看deployment

$ kubectl get deploy
NAME READY UP-TO-DATE AVAILABLE AGE
nfs-client-nfs-subdir-external-provisioner 1/1 1 1 2m28s

7.1.3 查看StorageClass

7.2 绑定PVC

7.2.1 配置文件 nfs-pvc.yaml

kind: PersistentVolumeClaim
apiVersion: v1
metadata:
name: nfs-pvc
spec:
storageClassName: nfs-client
accessModes:
- ReadWriteMany
resources:
requests:
storage: 1Mi

7.2.2 发布

$ kubectl apply -f nfs-pvc.yaml
persistentvolumeclaim/nfs-pvc created
# 查看状态发现状态 已经变成绑定
$ kubectl get pvc
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
nfs-pvc Bound pvc-... 1Mi RWX nfs-client 4s

# 虽然我们没有创建PV,但会自动创建
$ kubectl get pv
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS
pvc-... 1Mi RWX Delete Bound default/nfs-pvc nfs-client

7.3 Pod使用

7.3.1 配置文件nfs-pod.yaml

apiVersion: v1
kind: Pod
metadata:
name: nfs-pod-demo
spec:
containers:
- name: pvc-pod
image: busybox
volumeMounts:
- mountPath: /pvc
name: pvc-volume
args:
- /bin/sh
- -c
- echo "hello world..." > /pvc/hello-pvc; sleep 30000 # 写数据到文件
volumes: # 使用以下方式关联pvc
- name: pvc-volume
persistentVolumeClaim:
claimName: nfs-pvc # 和之前创建的pvc名称一致

7.3.2 发布 & 验证

# 发布资源
$ kubectl apply -f nfs-pod.yaml
pod/nfs-pod-demo created

nfs服务文件信息