创建NFS类型的PV
使用 nfs 类型的 pv
创建一个如下所示 nfs 类型的 PV 资源对象:
---
# nfs-volume.yaml
apiVersion: v1
kind: PersistentVolume
metadata:
name: nfs-pv
spec:
storageClassName: manual
capacity:
storage: 1Gi
accessModes:
- ReadWriteOnce
persistentVolumeReclaimPolicy: Retain
nfs:
# 指定 nfs 的挂载点
path: /data/nfs/tmp
# 指定 nfs 服务地址
server: 10.244.244.201
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: nfs-pvc
spec:
storageClassName: manual
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 1Gi
Pod 真正使用的是 PVC,而要使用 PVC 的前提就是必须要先和某个符合条件的 PV 进行一一绑定,比如存储容器、访问模式,以及 PV 和 PVC 的 storageClassName 字段必须一样,这样才能够进行绑定,当 PVC 和 PV 绑定成功后就可以直接使用这个 PVC 对象了:
# nfs-pod.yaml
apiVersion: v1
kind: Pod
metadata:
name: test-volumes
spec:
volumes:
- name: nfs
persistentVolumeClaim:
claimName: nfs-pvc
containers:
- name: web
image: nginx
ports:
- name: web
containerPort: 80
volumeMounts:
- name: nfs
subPath: test-volumes
mountPath: "/usr/share/nginx/html"
直接创建上面的资源对象即可:
> kubectl apply -f volume.yaml
> kubectl apply -f pod.yaml
> kubectl get pv nfs-pv
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
nfs-pv 1Gi RWO Retain Bound default/nfs-pvc manual 119s
> kubectl get pvc nfs-pvc
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
nfs-pvc Bound nfs-pv 1Gi RWO manual 2m5s
> kubectl get pods test-volumes -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
test-volumes 1/1 Running 0 51s 10.4.2.63 devnode2 <none> <none>
由于这里 PV 中的数据为空,所以挂载后会将 nginx 容器中的 /usr/share/nginx/html
目录覆盖,那么访问应用的时候就没有内容了:
> curl http://10.4.2.63
<html>
<head><title>403 Forbidden</title></head>
<body>
<center><h1>403 Forbidden</h1></center>
<hr><center>nginx/1.23.3</center>
</body>
</html>
可以在 PV 目录中添加一些内容:
# 在 nfs 服务器上面执行
> echo "nfs pv content" > /data/nfs/tmp/test-volumes/index.html
> curl http://10.4.2.63
nfs pv content
然后重新访问就有数据了,而且当 Pod 应用挂掉或者被删掉重新启动后数据还是存在的,因为数据已经持久化了。
原理
存储的整体架构
整个存储的架构可以用下图来说明:
- PV Controller:负责 PV/PVC 的绑定,并根据需求进行数据卷的 Provision/Delete 操作
- AD Controller:负责存储设备的 Attach/Detach 操作,将设备挂载到目标节点
- Volume Manager:管理卷的 Mount/Unmount 操作、卷设备的格式化等操作
- Volume Plugin:扩展各种存储类型的卷管理能力,实现第三方存储的各种操作能力和 Kubernetes 存储系统结合
上面使用的 NFS 就属于 In-Tree
这种方式
In-Tree
就是在 Kubernetes 源码内部实现的,和 Kubernetes 一起发布、管理的,但是更新迭代慢、灵活性比较差- 另外一种方式
Out-Of-Tree
是独立于 Kubernetes 的,目前主要有CSI
和FlexVolume
两种机制,开发者可以根据自己的存储类型实现不同的存储插件接入到 Kubernetes 中去,其中CSI
是现在也是以后主流的方式。
挂载流程
- (1)PV Controller 绑定 PV / PVC
PersistentVolumeController
会不断地循环去查看每一个 PVC,是不是已经处于 Bound(已绑定)状态。如果不是,那它就会遍历所有的、可用的 PV,并尝试将其与未绑定的 PVC 进行绑定,这样,Kubernetes 就可以保证用户提交的每一个 PVC,只要有合适的 PV 出现,它就能够很快进入绑定状态。而所谓将一个 PV 与 PVC 进行**绑定**,其实就是将这个 PV 对象的名字,填在了 PVC 对象的 spec.volumeName
字段上。
- (2)持久化目录创建
PV 和 PVC 绑定上了,那么又是如何将容器里面的数据进行持久化的呢,Docker 的 Volume 挂载其实就是**将一个宿主机上的目录和一个容器里的目录绑定挂载在了一起**,具有持久化功能当然就是指的宿主机上面的这个目录了,当容器被删除或者在其他节点上重建出来以后,这个目录里面的内容依然存在,所以一般情况下实现持久化是需要一个远程存储的,比如 NFS、Ceph 或者云厂商提供的磁盘等等。所以接下来需要做的就是持久化宿主机目录这个过程。
当 Pod 被调度到一个节点上后,节点上的 kubelet 组件就会为这个 Pod 创建它的 Volume 目录,默认情况下 kubelet 为 Volume 创建的目录在 kubelet 工作目录下面:
比如上面我们创建的 Pod 对应的 Volume 目录完整路径为:
要获取 Pod 的唯一标识 uid,可通过命令
kubectl get pod -o jsonpath={.metadata.uid} <pod名>
获取。
- (3)Attach 阶段
然后就需要根据我们的 Volume 类型来决定需要做什么操作了,假如后端存储使用的 Ceph RBD,那么 kubelet 就需要先将 Ceph 提供的 RBD 挂载到 Pod 所在的宿主机上面,这个阶段在 Kubernetes 中被称为 Attach 阶段。
- (4)Mount 阶段
Attach 阶段完成后,为了能够使用这个块设备,kubelet 还要进行第二个操作,即:**格式化**这个块设备,然后将它**挂载**到宿主机指定的挂载点上。这个挂载点,也就是上面我们提到的 Volume 的宿主机的目录。
将块设备格式化并挂载到 Volume 宿主机目录的操作,在 Kubernetes 中被称为 Mount 阶段。但是对于我们这里使用的 NFS 就更加简单了, 因为 NFS 存储并没有一个设备需要挂载到宿主机上面,所以这个时候 kubelet 就会直接进入第二个 Mount
阶段,相当于直接在宿主机上面执行如下的命令:
mount -t nfs 10.244.244.201:/data/nfs/tmp \
/var/lib/kubelet/pods/30dfc6f7-ba8a-400e-882f-8695d8c50c81/volumes/kubernetes.io~nfs/nfs-pv
同样可以在测试的 Pod 所在节点查看 Volume 的挂载信息:
> findmnt /var/lib/kubelet/pods/30dfc6f7-ba8a-400e-882f-8695d8c50c81/volumes/kubernetes.io~nfs/nfs-pv
TARGET SOURCE FSTYPE OPTIONS
/var/lib/kubelet/pods/30dfc6f7-ba8a-400e-882f-8695d8c50c81/volumes/kubernetes.io~nfs/nfs-pv 10.244.244.201:/data/nfs/tmp nfs4 rw,relatime,vers=4.2,rsize=1048576,wsize=1048576,namlen=255,hard,proto=tcp,timeo=600,retrans=2,sec=sys,clientaddr=10.244.244.21
可以看到这个 Volume 被挂载到了 NFS 下面,以后在这个目录里写入的所有文件,都会被保存在远程 NFS 服务器上。
这样在经过了上面的阶段过后,就得到了一个持久化的宿主机上面的 Volume 目录了,接下来 kubelet 只需要把这个 Volume 目录挂载到容器中对应的目录即可,这样就可以为 Pod 里的容器挂载这个持久化的 Volume 了,这一步其实也就相当于执行了如下所示的命令: