文章目录
-
- [Volumes (依赖本机来实现)](#Volumes (依赖本机来实现))
-
- 1.1EmptyDir
- [1.2 HostPath](#1.2 HostPath)
- [NFS 挂载(依赖远程服务来实现)](#NFS 挂载(依赖远程服务来实现))
-
- [安装 nfs](#安装 nfs)
- [挂载 NFS 共享目录](#挂载 NFS 共享目录)
- [配置文件 NFS 挂在到容器里去](#配置文件 NFS 挂在到容器里去)
- [高级存储 PV与PVC 概念详解](#高级存储 PV与PVC 概念详解)
- [PV与PVC 生命周期](#PV与PVC 生命周期)
- 创建PV与PVC以及关联Pod
- [StorageClass 存储类 动态创建NFS-PV案例](#StorageClass 存储类 动态创建NFS-PV案例)
-
-
- 集群环境
- 创建ServiceAccount
- 部署NFS-Subdir-External-Provisioner
- [创建 NFS StorageClass](#创建 NFS StorageClass)
- 查看nfs-server版本号
- 测试PVC使用StorageClass
-
在前面已经提到,容器的生命周期可能很短,会被频繁地创建和销毁。那么容器在销毁时,保存在容器中的数据也会被清除。这种结果对用户来说,在某些情况下是不乐意看到的。为了持久化保存容器的数据,kubernetes引入了Volume的概念。
Volume是Pod中能够被多个容器访问的共享目录,它被定义在Pod上,然后被一个Pod里的多个容器挂载到具体的文件目录下,kubernetes通过Volume实现同一个Pod中不同容器之间的数据共享以及数据的持久化存储。Volume的生命容器不与Pod中单个容器的生命周期相关,当容器终止或者重启时,Volume中的数据也不会丢失。
kubernetes的Volume支持多种类型,比较常见的有下面几个:
简单存储:EmptyDir、HostPath、NFS
高级存储:PV、PVC
配置存储:ConfigMap、Secret
Volumes (依赖本机来实现)
1.1EmptyDir
EmptyDir是最基础的Volume类型,一个EmptyDir就是Host上的一个空目录。
EmptyDir是在Pod被分配到Node时创建的,它的初始内容为空,并且无须指定宿主机上对应的目录文件,因为kubernetes会自动分配一个目录,当Pod销毁时, EmptyDir中的数据也会被永久删除。
EmptyDir用途如下:
- 临时空间,例如用于某些应用程序运行时所需的临时目录,且无须永久保留
- 一个容器需要从另一个容器中获取数据的目录(多容器共享目录)
接下来,通过一个容器之间文件共享的案例来使用一下EmptyDir。
在一个Pod中准备两个容器nginx和busybox,然后声明一个Volume分别挂在到两个容器的目录中,然后nginx容器负责向Volume中写日志,busybox中通过命令将日志内容读到控制台。
创建一个volume-emptydir.yaml
shell
apiVersion: v1
kind: Pod
metadata:
name: volume-emptydir
namespace: dev
spec:
containers:
- name: nginx
image: nginx:1.17.1
ports:
- containerPort: 80
volumeMounts: # 将logs-volume挂在到nginx容器中,对应的目录为 /var/log/nginx
- name: logs-volume
mountPath: /var/log/nginx
- name: busybox
image: busybox:1.30
command: ["/bin/sh","-c","tail -f /logs/access.log"] # 初始命令,动态读取指定文件中内容
volumeMounts: # 将logs-volume 挂在到busybox容器中,对应的目录为 /logs
- name: logs-volume
mountPath: /logs
volumes: # 声明volume, name为logs-volume,类型为emptyDir
- name: logs-volume
emptyDir: {}
shell
# 创建Pod
[root@k8s-master01 ~]# kubectl create -f volume-emptydir.yaml
pod/volume-emptydir created
# 查看pod
[root@k8s-master ~]# kubectl get pods volume-emptydir -n dev -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
volume-emptydir 2/2 Running 0 9s 10.244.1.103 k8s-node1 <none> <none>
# 通过podIp访问nginx
[root@k8s-master01 ~]# curl 10.244.1.103
......
# 通过kubectl logs命令查看指定容器的标准输出
# 进入到 BusyBox 容器中,检查是否可以动态读取 NGINX 的访问日志。
[root@k8s-master ~]# kubectl logs -f volume-emptydir -n dev -c busybox
10.244.0.0 - - [15/Mar/2024:09:19:56 +0000] "GET / HTTP/1.1" 200 612 "-" "curl/7.29.0" "-"
#日志文件存在且正在被动态读取,说明 BusyBox 容器能够成功读取 NGINX 的访问日志文件。
#获取 Pod 的 ID
[root@k8s-master ~]# kubectl get pods volume-emptydir -n dev -o jsonpath='{.metadata.uid}'
5562d13e-457d-44b1-8cae-649ba8ab884d
#检查容器卷的目录
[root@k8s-node1 logs]# ls /var/lib/kubelet/pods/5562d13e-457d-44b1-8cae-649ba8ab884d/volumes/kubernetes.io~empty-dir/
logs-volume
#删除pod 使用以下命令删除创建的 Pod,这将自动删除与之关联的容器卷:
[root@k8s-master ~]# kubectl delete pod volume-emptydir -n dev
pod "volume-emptydir" deleted
#确认容器卷删除 容器卷目录已经不存在,则说明容器卷已经成功删除
[root@k8s-node1 ~]# ls /var/lib/kubelet/pods/5562d13e-457d-44b1-8cae-649ba8ab884d/volumes/kubernetes.io~empty-dir/
ls: 无法访问/var/lib/kubelet/pods/5562d13e-457d-44b1-8cae-649ba8ab884d/volumes/kubernetes.io~empty-dir/: 没有那个文件或目录
1.2 HostPath
上节课提到,EmptyDir中数据不会被持久化,它会随着Pod的结束而销毁,如果想简单的将数据持久化到主机中,可以选择HostPath。
HostPath就是将Node主机中一个实际目录挂在到Pod中,以供容器使用,这样的设计就可以保证Pod销毁了,但是数据依据可以存在于Node主机上。
创建一个volumes-test-pd.yaml:
yaml
apiVersion: v1
kind: Pod
metadata:
name: test-pd
spec:
containers:
- image: nginx
name: nginx-volume
volumeMounts:
- mountPath: /test-pd # 挂载到容器的哪个目录
name: test-volume # 挂载哪个 volume
volumes:
- name: test-volume
hostPath: #与主机(节点)共享目录,加载主机中制定目录到容器中
path: /data # 节点中的目录
type: Directory # 检查类型,在挂载前对挂载目录做什么检查操作,有多种选项,默认为空字符串,不做任何检查
创建一个volume-hostpath.yaml:
json
apiVersion: v1
kind: Pod
metadata:
name: volume-hostpath
namespace: dev
spec:
containers:
- name: nginx
image: nginx:1.17.1
ports:
- containerPort: 80
volumeMounts:
- name: logs-volume
mountPath: /var/log/nginx
- name: busybox
image: busybox:1.30
command: ["/bin/sh","-c","tail -f /logs/access.log"]
volumeMounts:
- name: logs-volume
mountPath: /logs
volumes:
- name: logs-volume
hostPath:
path: /root/logs
type: DirectoryOrCreate # 目录存在就使用,不存在就先创建后使用
json
关于type的值的一点说明:
类型:
空字符串:默认类型,不做任何检查
DirectoryOrCreate:如果给定的 path 不存在,就创建一个 755 的空目录
Directory:这个目录必须存在
FileOrCreate:如果给定的文件不存在,则创建一个空文件,权限为 644
File:这个文件必须存在
Socket:UNIX 套接字,必须存在
CharDevice:字符设备,必须存在
BlockDevice:块设备,必须存在
json
# 创建Pod
[root@k8s-master volumes]# kubectl create -f volume-hostpath.yaml
pod/volume-hostpath created
[root@k8s-master volumes]# kubectl get pods volume-hostpath -n dev -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
volume-hostpath 2/2 Running 0 25s 10.244.1.102 k8s-node1 <none> <none>
#访问nginx
[root@k8s-master volumes]# curl 10.244.1.102
接下来就可以去host的/root/logs目录下查看存储的文件了
### 注意: 下面的操作需要到Pod所在的节点运行(案例中是node1)
[root@k8s-node1 ~]# ls /root/logs/
access.log error.log
# 同样的道理,如果在此目录下创建一个文件,到容器中也是可以看到的
[root@k8s-node1 logs]# vi test.txt
[root@k8s-node1 logs]# ls
access.log error.log test.txt
[root@k8s-master ~]# kubectl exec -it volume-hostpath -n dev -c busybox -- sh
/ # ls
bin dev etc home logs proc root sys tmp usr var
/ # cd logs
/logs # ls
access.log error.log test.txt
# 把容器删掉
[root@k8s-master ~]# kubectl delete pod volume-hostpath -n dev
pod "volume-hostpath" deleted
# 检查 /data 目录中的文件仍然存在
# 表示 HostPath 卷成功地将数据持久化存储在节点主机上,即使 Pod 已被删除
[root@k8s-node1 logs]# ls
access.log error.log test.txt
NFS 挂载(依赖远程服务来实现)
nfs 卷能将 NFS (网络文件系统) 挂载到你的 Pod 中。 不像 emptyDir 那样会在删除 Pod 的同时也会被删除,nfs 卷的内容在删除 Pod 时会被保存,卷只是被卸载。 这意味着 nfs 卷可以被预先填充数据,并且这些数据可以在 Pod 之间共享。
NFS是一个网络文件存储系统,可以搭建一台NFS服务器,然后将Pod中的存储直接连接到NFS系统上,这样的话,无论Pod在节点上怎么转移,只要Node跟NFS的对接没问题,数据就可以成功访问。
安装 nfs
每个服务器都需要安装
shell
# 安装 nfs
yum install nfs-utils -y
# 启动 nfs
systemctl start nfs-server
# 查看 nfs 版本
cat /proc/fs/nfsd/versions
#启用NFS服务器的自动启动:
sudo systemctl enable nfs-server
#验证NFS服务器状态:
systemctl status nfs-server
挂载 NFS 共享目录
- 在 Node1 上创建共享目录 (想要哪台服务器当共享目录就在哪个服务器上操作)
shell
[root@k8s-node1 home]# mkdir nfs
[root@k8s-node1 home]# ls
nfs
[root@k8s-node1 home]# cd nfs/
[root@k8s-node1 nfs]# mkdir rw
[root@k8s-node1 nfs]# mkdir ro
[root@k8s-node1 nfs]# ls
ro rw
- 设置 NFS 服务器的共享配置 export
shell
#node1下操作 编辑 /etc/exports 文件,配置 NFS 服务器的共享目录和访问权限。
vi /etc/exports
#在打开的编辑器中添加以下行来定义共享目录和权限:
/home/nfs/rw 192.168.235.0/24(rw,sync,no_subtree_check,no_root_squash)
/home/nfs/ro 192.168.235.0/24(ro,sync,no_subtree_check,no_root_squash)
/home/nfs/rw/www/wolfcode 192.168.235.0/24(rw,sync,no_subtree_check,no_root_squash)
- /home/nfs/rw 和 /home/nfs/ro 目录分别以读写(rw)和只读(ro)模式共享给 192.168.235.0/24 网段的所有主机。
- sync 选项表示写入数据时立即同步。
- no_subtree_check 选项禁止对子目录进行权限检查。
- no_root_squash 选项允许客户端使用 root 用户身份。
- 重新加载 NFS 服务器配置
shell
# 重新加载
exportfs -f
systemctl reload nfs-server
- 在 Node1 上创建测试文件
shell
#在 /home/nfs/ro 目录下创建一个包含内容的 文件,
[root@k8s-node1 ro]# vi example.md
- 在测试节点上挂载 NFS 共享目录
在其他测试节点上安装 nfs-utils 包,并挂载 NFS 共享目录。
shell
# 创建挂载点目录
mkdir -p /mnt/nfs/rw
mkdir -p /mnt/nfs/ro
# 挂载 NFS 共享目录
mount -t nfs 192.168.235.129:/home/nfs/rw /mnt/nfs/rw
mount -t nfs 192.168.235.129:/home/nfs/ro /mnt/nfs/ro
#将位于 IP 地址为 192.168.235.129 的 NFS 服务器上的 /home/nfs/rw 目录挂载到本地系统的 /mnt/nfs/rw 目录下。
#这里假设 /mnt/nfs/rw 是本地已经创建好的挂载点目录,用来接收远程共享的文件和数据。
shell
#在master下面查看 包含了你在 NFS 服务器上创建的文件,说明 NFS 挂载成功 实现了文件共享
[root@k8s-master ro]# ls
example.md
[root@k8s-master ro]# cat example.md
ABCDEFG123
[root@k8s-master ro]# touch master
touch: 无法创建"master": 只读文件系统
以上步骤完成后,你就在其他测试节点上成功挂载了 NFS 共享目录,并且可以在 /mnt/nfs/rw 和 /mnt/nfs/ro 目录下访问共享的文件和数据。
配置文件 NFS 挂在到容器里去
shell
[root@k8s-master volumes]# vi nfs-test-pd.yaml
yaml
apiVersion: v1
kind: Pod
metadata:
name: nfs-test-pd1
spec:
containers:
- image: nginx
name: test-container
volumeMounts:
- mountPath: /usr/share/nginx/html
name: test-volume
volumes:
- name: test-volume
nfs:
server: 192.168.235.129 # 网络存储服务地址
path: /home/nfs/rw/www/wolfcode # 网络存储路径
readOnly: false # 是否只读
- 在 k8s-node1 节点上设置 NFS 共享目录:
yaml
#首先,你在 k8s-node1 节点上创建了一个目录结构,
#并在其中创建了一个名为 index.html 的文件,内容为 <h1>Wolfcode</h1>。
[root@k8s-node1 ~]# mkdir -p /home/nfs/rw/www/wolfcode
[root@k8s-node1 ~]# echo '<h1>Wolfcode</h1>' >/home/nfs/rw/www/wolfcode/index.html
- 在另一台服务器上挂载 NFS 共享目录:
在另一台名为 k8s-master 的服务器上,你使用 mount 命令挂载了 k8s-node1 上的 NFS 共享目录。
yaml
#挂载 NFS 共享:在另一个服务器上使用 mount 命令挂载 NFS 共享。
#假设要将共享挂载到 /mnt/nfs 目录,可以执行以下命令
[root@k8s-master ]sudo mount -t nfs 192.168.235.129:/home/nfs/rw/www/wolfcode /mnt/nfs
- 创建并运行 Pod:
yaml
[root@k8s-master volumes]# kubectl create -f nfs-test-po.yaml
pod/nfs-test-pd1 created
[root@k8s-master volumes]# kubectl get po
NAME READY STATUS RESTARTS AGE
nfs-test-pd1 1/1 Running 0 51s
- 验证 Pod 访问 NFS 共享内容
yaml
[root@k8s-master volumes]# kubectl get po -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
nfs-test-pd1 1/1 Running 0 89s 10.244.1.125 k8s-node1 <none> <none>
# 请求到node1中nfs挂载的文件
[root@k8s-master volumes]# curl 10.244.1.125
<h1>Wolfcode</h1>
- 更新 NFS 共享内容并重新验证:
yaml
#在 k8s-node1 节点上,你修改了 index.html 文件的内容为 <h1>欢迎光临</h1>。
[root@k8s-node1 ~]# echo '<h1>欢迎光临</h1>' >/home/nfs/rw/www/wolfcode/index.html
#再次使用 curl 命令从 k8s-master 节点访问了 Pod nfs-test-pd1 的 IP 地址,
#并验证了可以访问到修改后的内容。
[root@k8s-master volumes]# curl 10.244.1.125
<h1>欢迎光临</h1>
- 删除pod 在node1上的文件任然存在 既能实现远程网络的文件共享,并且数据也不会丢失
yaml
kubectl delete pod <pod_name>
这样的话,无论Pod在节点上怎么转移,只要Node跟NFS的对接没问题,数据就可以成功访问。
高级存储 PV与PVC 概念详解
普通存方案:功能分散,没有标准 如下图:
前面已经学习了使用NFS提供存储,此时就要求用户会搭建NFS系统,并且会在yaml配置nfs。由于kubernetes支持的存储系统有很多,要求客户全都掌握,显然不现实。为了能够屏蔽底层存储实现的细节,方便用户使用, kubernetes引入PV和PVC两种资源对象。
- PV(Persistent Volume)是持久化卷的意思,是对底层的共享存储的一种抽象。一般情况下PV由kubernetes管理员进行创建和配置,它与底层具体的共享存储技术有关,并通过插件完成与共享存储的对接。
- PVC(Persistent Volume Claim)是持久卷声明的意思,是用户对于存储需求的一种声明。换句话说,PVC其实就是用户向kubernetes系统发出的一种资源需求申请。
使用了PV和PVC之后,工作可以得到进一步的细分:
- 存储:存储工程师维护
- PV: kubernetes管理员维护
- PVC:kubernetes用户维护
PV与PVC 生命周期
构建
- 静态构建
集群管理员创建若干 PV 卷。这些卷对象带有真实存储的细节信息, 并且对集群用户可用(可见)。PV 卷对象存在于 Kubernetes API 中,可供用户消费(使用)。
- 动态构建
如果集群中已经有的 PV 无法满足 PVC 的需求,那么集群会根据 PVC 自动构建一个 PV,该操作是通过 StorageClass 实现的。
想要实现这个操作,前提是 PVC 必须设置 StorageClass,否则会无法动态构建该 PV,可以通过启用 DefaultStorageClass 来实现 PV 的构建。
绑定
当用户创建一个 PVC 对象后,主节点会监测新的 PVC 对象,并且寻找与之匹配的 PV 卷,找到 PV 卷后将二者绑定在一起。
如果找不到对应的 PV,则需要看 PVC 是否设置 StorageClass 来决定是否动态创建 PV,若没有配置,PVC 就会一致处于未绑定状态,直到有与之匹配的 PV 后才会申领绑定关系。
使用
Pod 将 PVC 当作存储卷来使用,集群会通过 PVC 找到绑定的 PV,并为 Pod 挂载该卷。
Pod 一旦使用 PVC 绑定 PV 后,为了保护数据,避免数据丢失问题,PV 对象会受到保护,在系统中无法被删除。
回收策略
- 保留(Retain)
回收策略 Retain 使得用户可以手动回收资源。当 PersistentVolumeClaim 对象被删除时,PersistentVolume 卷仍然存在,对应的数据卷被视为"已释放(released)"。 由于卷上仍然存在这前一申领人的数据,该卷还不能用于其他申领。 管理员可以通过下面的步骤来手动回收该卷:
- 删除 PersistentVolume 对象。与之相关的、位于外部基础设施中的存储资产 (例如 AWS EBS、GCE PD、Azure Disk 或 Cinder 卷)在 PV 删除之后仍然存在。
- 根据情况,手动清除所关联的存储资产上的数据。
- 手动删除所关联的存储资产。
如果你希望重用该存储资产,可以基于存储资产的定义创建新的 PersistentVolume 卷对象。
- 删除(Delete)
对于支持 Delete 回收策略的卷插件,删除动作会将 PersistentVolume 对象从 Kubernetes 中移除,同时也会从外部基础设施(如 AWS EBS、GCE PD、Azure Disk 或 Cinder 卷)中移除所关联的存储资产。 动态制备的卷会继承其 StorageClass 中设置的回收策略, 该策略默认为 Delete。管理员需要根据用户的期望来配置 StorageClass; 否则 PV 卷被创建之后必须要被编辑或者修补。
- 回收(Recycle)
警告: 回收策略 Recycle 已被废弃。取而代之的建议方案是使用动态制备。
如果下层的卷插件支持,回收策略 Recycle 会在卷上执行一些基本的擦除 (rm -rf /thevolume/*)操作,之后允许该卷用于新的 PVC 申领。
创建PV与PVC以及关联Pod
PV是存储资源的抽象,下面是资源清单文件:
yaml
apiVersion: v1
kind: PersistentVolume #描述资源对象为PV类型
metadata:
name: pv0001 #PV的名字
spec:
capacity: #容量配置
storage: 5Gi # pv 的容量
volumeMode: Filesystem # 存储类型为文件系统
accessModes: # 访问模式:ReadWriteOnce、ReadWriteMany、ReadOnlyMany
- ReadOnlyMany # 可被单节点独写
persistentVolumeReclaimPolicy: Recycle # 回收策略
storageClassName: slow # 创建 PV 的存储类名,需要与 pvc 的相同
mountOptions: # 加载配置
- hard
- nfsvers=4.1
nfs: # 连接到 nfs
path: /data/nfs/rw/test-pv # 存储路径
server: 192.168.235.129 # nfs 服务地址
PV 的关键配置参数说明:
- 存储类型
底层实际存储的类型,kubernetes支持多种存储类型,每种存储类型的配置都有所差异
- 存储能力(capacity)
目前只支持存储空间的设置( storage=1Gi ),不过未来可能会加入IOPS、吞吐量等指标的配置
- 访问模式(accessModes)
用于描述用户应用对存储资源的访问权限,访问权限包括下面几种方式:
- ReadWriteOnce(RWO):读写权限,但是只能被单个节点挂载
- ReadOnlyMany(ROX): 只读权限,可以被多个节点挂载
- ReadWriteMany(RWX):读写权限,可以被多个节点挂载
需要注意的是,底层不同的存储类型可能支持的访问模式不同
- 回收策略(persistentVolumeReclaimPolicy)
当PV不再被使用了之后,对其的处理方式。目前支持三种策略:
- Retain (保留) 保留数据,需要管理员手工清理数据
- Recycle(回收) 清除 PV 中的数据,效果相当于执行 rm -rf /thevolume/*
- Delete (删除) 与 PV 相连的后端存储完成 volume 的删除操作,当然这常见于云服务商的存储服务
需要注意的是,底层不同的存储类型可能支持的回收策略不同
- 存储类别
PV可以通过storageClassName参数指定一个存储类别
- 具有特定类别的PV只能与请求了该类别的PVC进行绑定
- 未设定类别的PV则只能与不请求任何类别的PVC进行绑定
- 状态(status)
一个 PV 的生命周期中,可能会处于4中不同的阶段:
- Available(可用): 表示可用状态,还未被任何 PVC 绑定
- Bound(已绑定): 表示 PV 已经被 PVC 绑定
- Released(已释放): 表示 PVC 被删除,但是资源还未被集群重新声明
- Failed(失败): 表示该 PV 的自动回收失败
创建 PersistentVolume(PV)配置文件
shell
# 用上面哪个资源配置清单
[root@k8s-master volumes]# vi pv-nfs.yaml
[root@k8s-master volumes]# ls
nfs-test-po.yaml pv-nfs.yaml volume-hostpath.yaml volumes-test-pd.yaml
创建 PV
shell
[root@k8s-master volumes]# kubectl create -f pv-nfs.yaml
persistentvolume/pv0001 created
查看PV列表
shell
[root@k8s-master volumes]# kubectl get pv
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
pv0001 5Gi ROX Recycle Available slow 17s
# 可以看到STATUS 为Available(可用): 空闲,未被绑定
- 名称(NAME): PV 的唯一标识符。
- 容量(CAPACITY): PV 的存储容量。
- 访问模式(ACCESS MODES): 定义了 PV 可以被访问的方式,包括只读(RO)、读写(RWX)等。
- 回收策略(RECLAIM POLICY): 定义了当 PV 不再被使用时的回收行为,可以是回收(Recycle)、删除(Delete)等。
- 状态(STATUS): PV 的当前状态,这里是可用(Available),表示 PV 没有被绑定到任何 PVC 上,可以供其他 PVC 使用。
- CLAIM(CLAIM): 如果 PV 已经被 PVC 绑定,该字段显示绑定的 PVC 名称。
- 存储类(STORAGECLASS): 如果 PV 是动态配置的,该字段显示 PV 所属的存储类。
- 原因(REASON): 如果 PV 处于某种状态,该字段提供了该状态的原因。
- 年龄(AGE): PV 的创建时间以及 PV 在当前状态的持续时间。
PVC是资源的申请,用来声明对存储空间、访问模式、存储类别需求信息。下面是资源清单文件:
yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: nfs-pvc
spec:
accessModes:
- ReadOnlyMany # 权限需要与对应的 pv 相同
volumeMode: Filesystem
resources:
requests:
storage: 5Gi # 资源可以小于 pv 的,但是不能大于,如果大于就会匹配不到 pv
storageClassName: slow # 名字需要与对应的 pv 相同
# selector: # 使用选择器选择对应的 pv
# matchLabels:
# release: "stable"
# matchExpressions:
# - {key: environment, operator: In, values: [dev]}
PVC 的关键配置参数说明:
- 访问模式(accessModes)
用于描述用户应用对存储资源的访问权限
- 选择条件(selector)
通过Label Selector的设置,可使PVC对于系统中己存在的PV进行筛选
- 存储类别(storageClassName)
PVC在定义时可以设定需要的后端存储的类别,只有设置了该class的pv才能被系统选出
- 资源请求(Resources )
描述对存储资源的请求
创建 PersistentVolumeClaim(PVC)配置文件
yaml
[root@k8s-master volumes]# vi pvc-test.yaml
[root@k8s-master volumes]# ls
nfs-test-po.yaml pvc-test.yaml pv-nfs.yaml volume-hostpath.yaml volumes-test-pd.yaml
创建PVC
yaml
[root@k8s-master volumes]# kubectl create -f pvc-test.yaml
persistentvolumeclaim/nfs-pvc created
查看pvc列表
yaml
[root@k8s-master volumes]# kubectl get pvc
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
nfs-pvc Bound pv0001 5Gi ROX slow 9s
- 名称(NAME): PVC 的唯一标识符。
- 状态(STATUS): PVC 的当前状态,这里是 Bound,表示 PVC 已经成功绑定了一个 PV。
- 卷(VOLUME): 绑定的 PV 的名称。
- 容量(CAPACITY): PV 的存储容量。
- 访问模式(ACCESS MODES): 定义了 PV 可以被访问的方式,这里是只读(ROX)。
- 存储类(STORAGECLASS): PV 的存储类。
- 年龄(AGE): PVC 的创建时间以及 PVC 在当前状态的持续时间。
创建 pvc-test-pd.yaml, 使用pv
yaml
#因为之前pv的目录没有挂载共项目 在设置一遍
[root@k8s-node1 ~]# vi /etc/exports
/data/nfs/rw 192.168.235.0/24(rw,sync,no_subtree_check,no_root_squash)
[root@k8s-node1 ~]# mkdir -p /data/nfs/rw
[root@k8s-node1 ~]# cd /data/nfs/rw/
[root@k8s-node1 rw]# mkdir test-pv
# 重新加载
exportfs -f
systemctl reload nfs-server
[root@k8s-node1 rw]# ls
test-pv
[root@k8s-node1 rw]# cd test-pv
[root@k8s-node1 test-pv]# echo 'inited....' >index.html
yaml
apiVersion: v1
kind: Pod
metadata:
name: test-pvc-pd
spec:
containers:
- image: nginx
name: nginx-volume
volumeMounts:
- mountPath: /usr/share/nginx/html # 挂载到容器的哪个目录
name: test-volume # 挂载哪个 volume
volumes:
- name: test-volume
persistentVolumeClaim:
claimName: nfs-pvc # pvc 的名称
yaml
[root@k8s-master volumes]# vi pvc-test-pd.yaml
[root@k8s-master volumes]# ls
nfs-test-po.yaml pvc-test-pd.yaml pvc-test.yaml pv-nfs.yaml volume-hostpath.yaml volumes-test-pd.yaml
[root@k8s-master volumes]# kubectl create -f pvc-test-pd.yaml
pod/test-pvc-pd created
[root@k8s-master volumes]# kubectl get pod -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
nfs-test-pd1 1/1 Running 0 6h45m 10.244.1.128 k8s-node1 <none> <none>
#共享成功
[root@k8s-master volumes]# curl 10.244.1.130
inited....
StorageClass 存储类 动态创建NFS-PV案例
集群环境
shell
[root@k8s-master ~]# kubectl cluster-info
Kubernetes control plane is running at https://192.168.235.128:6443
CoreDNS is running at https://192.168.235.128:6443/api/v1/namespaces/kube-system/services/kube-dns:dns/proxy
To further debug and diagnose cluster problems, use 'kubectl cluster-info dump'.
[root@k8s-master ~]# kubectl get nodes -owide
NAME STATUS ROLES AGE VERSION INTERNAL-IP EXTERNAL-IP OS-IMAGE KERNEL-VERSION CONTAINER-RUNTIME
k8s-master Ready control-plane,master 92d v1.23.17 192.168.235.128 <none> CentOS Linux 7 (Core) 3.10.0-693.el7.x86_64 docker://20.10.23
k8s-node1 Ready <none> 92d v1.23.17 192.168.235.129 <none> CentOS Linux 7 (Core) 3.10.0-693.el7.x86_64 docker://20.10.23
创建ServiceAccount
现在的 Kubernetes 集群大部分是基于 RBAC 的权限控制,所以我们需要创建一个拥有一定权限的 ServiceAccount 与后面要部署的 NFS Subdir Externa Provisioner 组件绑定。
注意:ServiceAccount是必须的,否则将不会动态创建PV,PVC状态一直为Pending
RBAC 资源文件 nfs-rbac.yaml
shell
apiVersion: v1
kind: ServiceAccount
metadata:
name: nfs-client-provisioner
namespace: dev
---
kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: nfs-client-provisioner-runner
rules:
- apiGroups: [""]
resources: ["persistentvolumes"]
verbs: ["get", "list", "watch", "create", "delete"]
- apiGroups: [""]
resources: ["persistentvolumeclaims"]
verbs: ["get", "list", "watch", "update"]
- apiGroups: ["storage.k8s.io"]
resources: ["storageclasses"]
verbs: ["get", "list", "watch"]
- apiGroups: [""]
resources: ["events"]
verbs: ["create", "update", "patch"]
---
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: run-nfs-client-provisioner
subjects:
- kind: ServiceAccount
name: nfs-client-provisioner
namespace: dev
roleRef:
kind: ClusterRole
name: nfs-client-provisioner-runner
apiGroup: rbac.authorization.k8s.io
---
kind: Role
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: leader-locking-nfs-client-provisioner
namespace: dev
rules:
- apiGroups: [""]
resources: ["endpoints"]
verbs: ["get", "list", "watch", "create", "update", "patch"]
---
kind: RoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: leader-locking-nfs-client-provisioner
namespace: dev
subjects:
- kind: ServiceAccount
name: nfs-client-provisioner
namespace: dev
roleRef:
kind: Role
name: leader-locking-nfs-client-provisioner
apiGroup: rbac.authorization.k8s.io
shell
[root@k8s-master volumes]# kubectl apply -f nfs-rbac.yaml
serviceaccount/nfs-client-provisioner created
clusterrole.rbac.authorization.k8s.io/nfs-client-provisioner-runner created
clusterrolebinding.rbac.authorization.k8s.io/run-nfs-client-provisioner created
role.rbac.authorization.k8s.io/leader-locking-nfs-client-provisioner created
rolebinding.rbac.authorization.k8s.io/leader-locking-nfs-client-provisioner created
部署NFS-Subdir-External-Provisioner
我们以master(10.211.55.11)为nfs服务器,共享目录为/root/data/nfs,StorageClass名称为storage-nfs 部署NFS-Subdir-External-Provisioner
创建nfs-provisioner-deploy.yaml
shell
kind: Deployment
apiVersion: apps/v1
metadata:
name: nfs-client-provisioner
namespace: dev
spec:
replicas: 1
selector:
matchLabels:
app: nfs-client-provisioner
strategy:
type: Recreate #设置升级策略为删除再创建(默认为滚动更新)
template:
metadata:
labels:
app: nfs-client-provisioner
spec:
serviceAccountName: nfs-client-provisioner #上一步创建的ServiceAccount名称
containers:
- name: nfs-client-provisioner
image: registry.cn-beijing.aliyuncs.com/mydlq/nfs-subdir-external-provisioner:v4.0.0
volumeMounts:
- name: nfs-client-root
mountPath: /persistentvolumes
env:
- name: PROVISIONER_NAME # Provisioner的名称,以后设置的storageclass要和这个保持一致
value: storage-nfs
- name: NFS_SERVER # NFS服务器地址,需和valumes参数中配置的保持一致
value: 192.168.235.129
- name: NFS_PATH # NFS服务器数据存储目录,需和valumes参数中配置的保持一致
value: /data/nfs/rw
- name: ENABLE_LEADER_ELECTION
value: "true"
volumes:
- name: nfs-client-root
nfs:
server: 192.168.235.129 # NFS服务器地址
path: /data/nfs/rw # NFS共享目录
shell
[root@k8s-master volumes]# kubectl get deploy,pod -n dev
NAME READY UP-TO-DATE AVAILABLE AGE
deployment.apps/nfs-client-provisioner 1/1 1 1 7m54s
NAME READY STATUS RESTARTS AGE
pod/nfs-client-provisioner-5696db47dc-5bjt8 1/1 Running 0 7m54s
创建 NFS StorageClass
我们在创建 PVC 时经常需要指定 storageClassName 名称,这个参数配置的就是一个 StorageClass 资源名称,PVC 通过指定该参数来选择使用哪个 StorageClass,并与其关联的 Provisioner 组件来动态创建 PV 资源。所以,这里我们需要提前创建一个 Storagelcass 资源。
创建nfs-storageclass.yaml
shell
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
namespace: dev
name: nfs-storage
annotations:
storageclass.kubernetes.io/is-default-class: "false" ## 是否设置为默认的storageclass
provisioner: storage-nfs ## 动态卷分配者名称,必须和上面创建的deploy中环境变量"PROVISIONER_NAME"变量值一致
parameters:
archiveOnDelete: "true" ## 设置为"false"时删除PVC不会保留数据,"true"则保留数据
#reclaimPolicy: Retain # 回收策略,默认为 Delete 可以配置为 Retain
#volumeBindingMode: Immediate # 默认为 Immediate,表示创建 PVC 立即进行绑定,只有 azuredisk 和 AWSelasticblockstore 支持其他值
mountOptions:
- hard ## 指定为硬挂载方式
- nfsvers=4 ## 指定NFS版本,这个需要根据NFS Server版本号设置
yaml
# 创建
[root@k8s-master volumes]# kubectl apply -f nfs-storageclass.yaml
storageclass.storage.k8s.io/nfs-storage created
[root@k8s-master ~]#
# 查看
[root@k8s-master volumes]# kubectl get sc -n dev
NAME PROVISIONER RECLAIMPOLICY VOLUMEBINDINGMODE ALLOWVOLUMEEXPANSION AGE
nfs-storage storage-nfs Delete Immediate false 15s
查看nfs-server版本号
yaml
# nfs 服务器版本号查看 其中 "Server nfs v4" 说明版本为4
[root@k8s-master volumes]# nfsstat -v
测试PVC使用StorageClass
创建storage-pvc.yaml
yaml
kind: PersistentVolumeClaim
apiVersion: v1
metadata:
name: storage-pvc
namespace: dev
spec:
storageClassName: nfs-storage ## 需要与上面创建的storageclass的名称一致
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 1Mi
执行效果
yaml
# 创建
[root@k8s-master ~]# kubectl apply -f storage-pvc.yaml
persistentvolumeclaim/storage-pvc created
[root@k8s-master ~]#
[root@k8s-master ~]#
# 查看pvc
[root@k8s-master volumes]# kubectl get pvc -n dev
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
storage-pvc Bound pvc-fc513144-b532-4404-8c24-6cbe71a197c5 1Mi RWO nfs-storage 14s
# 查看是否动态创建了pv
[root@k8s-master volumes]# kubectl get pv -n dev
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
pv0001 5Gi ROX Recycle Bound default/nfs-pvc slow 24h
pvc-fc513144-b532-4404-8c24-6cbe71a197c5 1Mi RWO Delete Bound dev/storage-pvc nfs-storage 24s
# 查看共享目录是否动态创建了文件
[root@k8s-node1 ~]# cd /data/nfs/rw
[root@k8s-node1 rw]# ls
dev-storage-pvc-pvc-fc513144-b532-4404-8c24-6cbe71a197c5 test-p